use std::sync::Arc;

use bytes::{Bytes, BytesMut};

use super::resident::{OwnedBatchBody, PendingCounts, PendingResponseLease};
use super::{CarrierLane, DownBatch, WebSession};
use crate::config::WebLimitsConfig;
use crate::web::frame::FrameType;
use crate::web::manager::ManagerError;

/// Stages one bounded lane batch and transfers its accounting into response ownership.
pub(super) fn take_lane_down_batch(
    session: &WebSession,
    limits: &WebLimitsConfig,
    lane: &mut CarrierLane,
    cursor: u64,
    carrier_health_eligible: bool,
) -> Result<DownBatch, ManagerError> {
    let next_cursor = lane
        .down_cursor
        .checked_add(1)
        .ok_or(ManagerError::Protocol)?;
    let mut count = 0usize;
    let mut body_len = 0usize;
    for queued in &lane.pending_frames {
        if count >= limits.max_frames_per_body
            || (count != 0
                && body_len.saturating_add(queued.encoded.len()) > limits.carrier_batch_bytes)
        {
            break;
        }
        body_len += queued.encoded.len();
        count += 1;
    }
    let Some(manager) = session.manager.upgrade() else {
        return Err(ManagerError::Closed);
    };
    let Some(_staging) = manager.try_downlink_staging_budget(body_len) else {
        return Err(ManagerError::Backpressure);
    };
    let mut body = BytesMut::with_capacity(body_len);
    let mut data_bytes = 0usize;
    let mut data_items = 0usize;
    let mut control_bytes = 0usize;
    let mut control_items = 0usize;
    for index in 0..count {
        let Some(queued) = lane.pending_frames.get(index) else {
            break;
        };
        if queued.frame_type == FrameType::Window
            && lane.pending_windows.get(&queued.stream_id) == Some(&index)
        {
            lane.pending_windows.remove(&queued.stream_id);
        }
    }
    for _ in 0..count {
        let Some(queued) = lane.pending_frames.pop_front() else {
            break;
        };
        body.extend_from_slice(&queued.encoded);
        if queued.control {
            control_bytes += queued.cost;
            control_items += 1;
        } else {
            data_bytes += queued.cost;
            data_items += 1;
        }
    }
    for index in lane.pending_windows.values_mut() {
        *index = index.saturating_sub(count);
    }
    lane.down_cursor = next_cursor;
    let counts = PendingCounts {
        data_bytes,
        data_items,
        control_bytes,
        control_items,
    };
    let lease = PendingResponseLease::new(session, counts, Some(Arc::clone(&lane.resident)));
    let body = Bytes::from_owner(OwnedBatchBody::new(body.freeze(), Arc::clone(&lease)));
    Ok(DownBatch {
        body,
        lease,
        base_cursor: cursor,
        next_cursor,
        data_bytes,
        data_items,
        control_bytes,
        control_items,
        carrier_health_eligible,
    })
}
