use std::collections::HashMap;
use std::sync::Arc;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};

use anyhow::{Result, anyhow, bail};
use tokio::io;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufWriter};
use tokio::sync::{broadcast, watch};
use tokio::time::timeout;
use tracing::{debug, error, info, warn};

use crate::data_model::resources::ip_resources::IPAddress;
use crate::rtr::cache::{
    Delta, SerialResult, SharedRtrCache, validate_payload_update_refs_for_rtr,
    validate_payloads_for_rtr,
};
use crate::rtr::error_type::ErrorCode;
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Timing};
use crate::rtr::pdu::{
    Aspa as AspaPdu, CacheReset, CacheResponse, EndOfData, ErrorReport, Flags, HEADER_LEN, Header,
    IPv4Prefix, IPv6Prefix, ResetQuery, RouterKey as RouterKeyPdu, SerialNotify, SerialQuery,
};

const SUPPORTED_MAX_VERSION: u8 = 2;
const SUPPORTED_MIN_VERSION: u8 = 0;

const ANNOUNCE_FLAG: u8 = 1;
const WITHDRAW_FLAG: u8 = 0;

/// Per-session notify rate limit: no more than once per minute.
const NOTIFY_MIN_INTERVAL: Duration = Duration::from_secs(60);
/// Limit captured offending PDU bytes in memory for error reporting.
const MAX_OFFENDING_PDU_CAPTURE: usize = 4096;
/// Log rate limiting window and burst for repeated high-frequency events.
const LOG_RATE_LIMIT_WINDOW: Duration = Duration::from_secs(30);
const LOG_RATE_LIMIT_BURST: u32 = 10;

static SESSION_LOG_LIMITERS: OnceLock<Mutex<HashMap<&'static str, LogBucket>>> = OnceLock::new();

#[derive(Debug, Clone, Copy)]
struct LogBucket {
    window_start: Instant,
    emitted: u32,
    suppressed: u32,
}

fn allow_rate_limited_log(event: &'static str) -> Option<u32> {
    let now = Instant::now();
    let lock = SESSION_LOG_LIMITERS.get_or_init(|| Mutex::new(HashMap::new()));
    let mut map = match lock.lock() {
        Ok(guard) => guard,
        Err(poisoned) => poisoned.into_inner(),
    };

    let bucket = map.entry(event).or_insert_with(|| LogBucket {
        window_start: now,
        emitted: 0,
        suppressed: 0,
    });

    if now.duration_since(bucket.window_start) >= LOG_RATE_LIMIT_WINDOW {
        let suppressed = bucket.suppressed;
        bucket.window_start = now;
        bucket.emitted = 1;
        bucket.suppressed = 0;
        return Some(suppressed);
    }

    if bucket.emitted < LOG_RATE_LIMIT_BURST {
        bucket.emitted += 1;
        let suppressed = bucket.suppressed;
        bucket.suppressed = 0;
        Some(suppressed)
    } else {
        bucket.suppressed += 1;
        None
    }
}

fn warn_rate_limited<F>(event: &'static str, build: F)
where
    F: FnOnce() -> String,
{
    if let Some(suppressed) = allow_rate_limited_log(event) {
        if suppressed > 0 {
            warn!(
                "RTR session log rate-limited: event={}, suppressed_count={}",
                event, suppressed
            );
        }
        warn!("{}", build());
    }
}

fn error_rate_limited<F>(event: &'static str, build: F)
where
    F: FnOnce() -> String,
{
    if let Some(suppressed) = allow_rate_limited_log(event) {
        if suppressed > 0 {
            warn!(
                "RTR session log rate-limited: event={}, suppressed_count={}",
                event, suppressed
            );
        }
        error!("{}", build());
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SessionState {
    Connected,
    Established,
    Closed,
}

pub struct RtrSession<S> {
    cache: SharedRtrCache,
    version: Option<u8>,
    stream: S,
    state: SessionState,
    notify_rx: broadcast::Receiver<()>,
    shutdown_rx: watch::Receiver<bool>,
    last_notify_at: Option<Instant>,
    transport_timeout_override: Option<Duration>,
}

impl<S> RtrSession<S>
where
    S: AsyncRead + AsyncWrite + Unpin,
{
    pub fn new(
        cache: SharedRtrCache,
        stream: S,
        notify_rx: broadcast::Receiver<()>,
        shutdown_rx: watch::Receiver<bool>,
    ) -> Self {
        Self {
            cache,
            version: None,
            stream,
            state: SessionState::Connected,
            notify_rx,
            shutdown_rx,
            last_notify_at: None,
            transport_timeout_override: None,
        }
    }

    pub fn with_transport_timeout(mut self, timeout: Duration) -> Self {
        self.transport_timeout_override = Some(timeout);
        self
    }

    pub async fn run(mut self) -> Result<()> {
        let result = self.run_inner().await;
        self.close_transport().await;
        result
    }

    async fn run_inner(&mut self) -> Result<()> {
        info!("RTR session started: {}", self.session_summary());
        loop {
            let transport_timeout = self.transport_timeout();
            tokio::select! {
                changed = self.shutdown_rx.changed() => {
                    match changed {
                        Ok(()) => {
                            if *self.shutdown_rx.borrow() {
                                info!("RTR session closing due to service shutdown: {}", self.session_summary());
                                self.state = SessionState::Closed;
                                return Ok(());
                            }
                        }
                        Err(_) => {
                            info!("RTR session closing because shutdown channel closed: {}", self.session_summary());
                            self.state = SessionState::Closed;
                            return Ok(());
                        }
                    }
                }

                header_res = async {
                    // draft-ietf-sidrops-8210bis-25 Section 6 allows routers to wait up to
                    // Refresh Interval before polling again (recommended default: 3600s).
                    // In an established session, a long quiet period is therefore expected and
                    // must not be treated as a transport stall.
                    //
                    // We only enforce transport timeout before session establishment. Once the
                    // session is established, we wait indefinitely for the next query header.
                    if self.state == SessionState::Established {
                        Header::read_raw(&mut self.stream).await
                    } else {
                        timeout(transport_timeout, Header::read_raw(&mut self.stream))
                            .await
                            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "transport read timed out"))?
                    }
                } => {
                    let raw_header = match header_res {
                        Ok(raw) => raw,
                        Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
                            info!("RTR session closed by peer before header read completed: {}", self.session_summary());
                            self.state = SessionState::Closed;
                            return Ok(());
                        }
                        Err(err) if err.kind() == io::ErrorKind::TimedOut => {
                            warn_rate_limited("session_header_timeout_waiting", || {
                                format!(
                                    "RTR session transport timeout while waiting for header: state={}, negotiated_version={:?}",
                                    self.state_name(),
                                    self.version
                                )
                            });
                            self.handle_transport_timeout(&[]).await?;
                            return Ok(());
                        }
                        Err(err) => {
                            warn_rate_limited("session_header_read_failed", || {
                                format!(
                                    "RTR session failed to read header: err={}, state={}, negotiated_version={:?}",
                                    err,
                                    self.state_name(),
                                    self.version
                                )
                            });
                            self.state = SessionState::Closed;
                            return Ok(());
                        }
                    };
                    let header = match Header::from_raw(raw_header) {
                        Ok(h) => h,
                        Err(err) => {
                            warn_rate_limited("session_header_invalid", || {
                                format!(
                                    "RTR session received invalid header: err={}, state={}, negotiated_version={:?}",
                                    err,
                                    self.state_name(),
                                    self.version
                                )
                            });
                            self.handle_header_read_error(raw_header, err).await?;
                            return Ok(());
                        }
                    };
                    debug!(
                        "RTR session received header: pdu={}, version={}, length={}, state={}",
                        header.pdu(),
                        header.version(),
                        header.length(),
                        self.state_name()
                    );
                    let header_prefix = header.as_ref();

                    if self.state == SessionState::Connected {
                        self.handle_first_pdu(header).await?;
                        if self.state == SessionState::Closed {
                            info!("RTR session closed during first-PDU handling: {}", self.session_summary());
                            return Ok(());
                        }
                        continue;
                    }

                    if self.state != SessionState::Established {
                        self.state = SessionState::Closed;
                        error!("RTR session entered invalid state: {}", self.session_summary());
                        bail!("session is not in a valid state to receive PDUs");
                    }

                    self.ensure_established_version(header).await?;

                    match header.pdu() {
                        ResetQuery::PDU => {
                            let query = match timeout(
                                self.transport_timeout(),
                                ResetQuery::read_payload(header, &mut self.stream),
                            )
                            .await
                            {
                                Ok(Ok(query)) => query,
                                Ok(Err(err)) => {
                                    self.handle_pdu_read_error(header, err).await?;
                                    return Ok(());
                                }
                                Err(_) => {
                                    self.handle_transport_timeout(header_prefix).await?;
                                    return Ok(());
                                }
                            };
                            self.handle_reset_query(query.as_ref()).await?;
                        }

                        SerialQuery::PDU => {
                            let query = match timeout(
                                self.transport_timeout(),
                                SerialQuery::read_payload(header, &mut self.stream),
                            )
                            .await
                            {
                                Ok(Ok(query)) => query,
                                Ok(Err(err)) => {
                                    self.handle_pdu_read_error(header, err).await?;
                                    return Ok(());
                                }
                                Err(_) => {
                                    self.handle_transport_timeout(header_prefix).await?;
                                    return Ok(());
                                }
                            };
                            let session_id = query.session_id();
                            let serial = query.serial_number();
                            self.handle_serial(header.version(), session_id, serial, query.as_ref()).await?;
                        }

                        ErrorReport::PDU => {
                            let _ = timeout(
                                self.transport_timeout(),
                                ErrorReport::skip_payload(header, &mut self.stream),
                            )
                            .await;
                            info!("RTR session received ErrorReport from peer, closing session: {}", self.session_summary());
                            self.state = SessionState::Closed;
                            return Ok(());
                        }

                        _ => {
                            let offending = self.read_full_pdu_bytes(header).await?;
                            let version = self.version.ok_or_else(|| anyhow!("missing negotiated version"))?;

                            self.send_error(
                                version,
                                ErrorCode::UnsupportedPduType,
                                &offending,
                                &[],
                            )
                            .await?;

                            warn_rate_limited("session_unsupported_pdu", || {
                                format!(
                                    "RTR session received unsupported PDU type {}, closing session: state={}, negotiated_version={:?}",
                                    header.pdu(),
                                    self.state_name(),
                                    self.version
                                )
                            });
                            self.state = SessionState::Closed;
                            return Ok(());
                        }
                    }
                }

                notify_res = self.notify_rx.recv(),
                if self.state == SessionState::Established && self.version.is_some() => {
                    match notify_res {
                        Ok(()) => {
                            debug!(
                                "RTR session handling cache update notify: state={}, negotiated_version={:?}",
                                self.state_name(),
                                self.version
                            );
                            self.handle_notify().await?;
                        }
                        Err(broadcast::error::RecvError::Lagged(_)) => {
                            warn_rate_limited("session_notify_lagged", || {
                                format!(
                                    "RTR session lagged on notify channel, forcing notify handling: state={}, negotiated_version={:?}",
                                    self.state_name(),
                                    self.version
                                )
                            });
                            self.handle_notify().await?;
                        }
                        Err(broadcast::error::RecvError::Closed) => {
                            debug!(
                                "RTR session notify channel closed, keeping session alive: state={}, negotiated_version={:?}",
                                self.state_name(),
                                self.version
                            );
                            // keep session alive
                        }
                    }
                }
            }
        }
    }

    async fn close_transport(&mut self) {
        if let Err(err) = self.stream.shutdown().await {
            debug!(
                "RTR session transport shutdown returned error: err={}, {}",
                err,
                self.session_summary()
            );
        } else {
            debug!(
                "RTR session transport shutdown completed: {}",
                self.session_summary()
            );
        }
    }

    async fn negotiate_version(&mut self, router_version: u8) -> io::Result<u8> {
        if let Some(current) = self.version {
            if current == router_version {
                return Ok(current);
            }

            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "protocol version changed after negotiation: established={}, received={}",
                    current, router_version
                ),
            ));
        }

        if router_version > SUPPORTED_MAX_VERSION {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "router version {} higher than cache max {}",
                    router_version, SUPPORTED_MAX_VERSION
                ),
            ));
        }

        self.version = Some(router_version);
        info!(
            "RTR session negotiated protocol version: negotiated_version={}, state={}",
            router_version,
            self.state_name()
        );
        Ok(router_version)
    }

    async fn ensure_established_version(&mut self, header: Header) -> Result<()> {
        let established = self
            .version
            .ok_or_else(|| anyhow!("session version not negotiated"))?;

        if header.version() == established {
            return Ok(());
        }

        warn_rate_limited("session_unexpected_protocol_version", || {
            format!(
                "RTR session received unexpected protocol version in established session: established_version={}, received_version={}, pdu={}",
                established,
                header.version(),
                header.pdu()
            )
        });

        if header.pdu() != ErrorReport::PDU {
            let offending = self.read_full_pdu_bytes(header).await?;
            let _ = self
                .send_unexpected_version(established, &offending, header.version())
                .await;
        }

        self.state = SessionState::Closed;
        bail!(
            "protocol version changed within session: established={}, received={}",
            established,
            header.version()
        );
    }

    async fn send_unsupported_version(
        &mut self,
        cache_version: u8,
        offending_pdu: &[u8],
        received_version: u8,
    ) -> io::Result<()> {
        let msg = format!(
            "unsupported protocol version {}, cache supports versions {}..={}",
            received_version, SUPPORTED_MIN_VERSION, SUPPORTED_MAX_VERSION
        );

        self.send_error(
            cache_version,
            ErrorCode::UnsupportedProtocolVersion,
            offending_pdu,
            msg.as_bytes(),
        )
        .await
    }

    async fn send_unexpected_version(
        &mut self,
        established_version: u8,
        offending_pdu: &[u8],
        received_version: u8,
    ) -> io::Result<()> {
        let msg = format!(
            "unexpected protocol version {}, established version is {}",
            received_version, established_version
        );

        self.send_error(
            established_version,
            ErrorCode::UnexpectedProtocolVersion,
            offending_pdu,
            msg.as_bytes(),
        )
        .await
    }

    async fn send_corrupt_session_id(
        &mut self,
        offending_pdu: &[u8],
        received_session_id: u16,
        expected_session_id: u16,
    ) -> io::Result<()> {
        let version = self.version()?;
        let msg = format!(
            "session id mismatch in established session: received={}, expected={}",
            received_session_id, expected_session_id
        );

        self.send_error(
            version,
            ErrorCode::CorruptData,
            offending_pdu,
            msg.as_bytes(),
        )
        .await
    }

    async fn send_corrupt_data(
        &mut self,
        version: u8,
        offending_pdu: &[u8],
        detail: &[u8],
    ) -> io::Result<()> {
        self.send_error(version, ErrorCode::CorruptData, offending_pdu, detail)
            .await
    }

    async fn send_no_data_available(
        &mut self,
        offending_pdu: &[u8],
        detail: &'static str,
    ) -> io::Result<()> {
        let version = self.version()?;
        self.send_error(
            version,
            ErrorCode::NoDataAvailable,
            offending_pdu,
            detail.as_bytes(),
        )
        .await
    }

    async fn handle_first_pdu(&mut self, header: Header) -> Result<()> {
        info!(
            "RTR session processing first PDU: pdu={}, version={}, length={}",
            header.pdu(),
            header.version(),
            header.length()
        );
        match header.pdu() {
            ResetQuery::PDU => {
                let version = header.version();
                let query = match timeout(
                    self.transport_timeout(),
                    ResetQuery::read_payload(header, &mut self.stream),
                )
                .await
                {
                    Ok(Ok(query)) => query,
                    Ok(Err(err)) => {
                        self.handle_first_pdu_read_error(header, err).await?;
                        return Ok(());
                    }
                    Err(_) => {
                        self.handle_transport_timeout(header.as_ref()).await?;
                        return Ok(());
                    }
                };
                if version > SUPPORTED_MAX_VERSION {
                    self.send_unsupported_version(SUPPORTED_MAX_VERSION, query.as_ref(), version)
                        .await?;
                    self.state = SessionState::Closed;
                    bail!(
                        "router version {} higher than cache max {}",
                        version,
                        SUPPORTED_MAX_VERSION
                    );
                }
                self.negotiate_version(version).await?;
                self.handle_reset_query(query.as_ref()).await?;
                self.state = SessionState::Established;
                info!(
                    "RTR session established after Reset Query: negotiated_version={}, {}",
                    version,
                    self.session_summary()
                );
            }

            SerialQuery::PDU => {
                let version = header.version();
                let query = match timeout(
                    self.transport_timeout(),
                    SerialQuery::read_payload(header, &mut self.stream),
                )
                .await
                {
                    Ok(Ok(query)) => query,
                    Ok(Err(err)) => {
                        self.handle_first_pdu_read_error(header, err).await?;
                        return Ok(());
                    }
                    Err(_) => {
                        self.handle_transport_timeout(header.as_ref()).await?;
                        return Ok(());
                    }
                };
                if version > SUPPORTED_MAX_VERSION {
                    self.send_unsupported_version(SUPPORTED_MAX_VERSION, query.as_ref(), version)
                        .await?;
                    self.state = SessionState::Closed;
                    bail!(
                        "router version {} higher than cache max {}",
                        version,
                        SUPPORTED_MAX_VERSION
                    );
                }
                self.negotiate_version(version).await?;

                let session_id = query.session_id();
                let serial = query.serial_number();
                self.handle_serial(version, session_id, serial, query.as_ref())
                    .await?;
                self.state = SessionState::Established;
                info!(
                    "RTR session established after Serial Query: negotiated_version={}, client_session_id={}, client_serial={}, {}",
                    version,
                    session_id,
                    serial,
                    self.session_summary()
                );
            }

            ErrorReport::PDU => {
                let _ = timeout(
                    self.transport_timeout(),
                    ErrorReport::skip_payload(header, &mut self.stream),
                )
                .await;
                self.state = SessionState::Closed;
                bail!("received ErrorReport before session establishment");
            }

            _ => {
                let offending = self.read_full_pdu_bytes(header).await?;
                let err_version = if (SUPPORTED_MIN_VERSION..=SUPPORTED_MAX_VERSION)
                    .contains(&header.version())
                {
                    header.version()
                } else {
                    SUPPORTED_MAX_VERSION
                };

                let _ = self
                    .send_error(
                        err_version,
                        ErrorCode::InvalidRequest,
                        &offending,
                        b"first PDU must be Reset Query or Serial Query",
                    )
                    .await;

                self.state = SessionState::Closed;
                bail!("first PDU must be Reset Query or Serial Query");
            }
        }

        Ok(())
    }

    async fn handle_reset_query(&mut self, offending_pdu: &[u8]) -> Result<()> {
        debug!(
            "RTR session received Reset Query: negotiated_version={:?}, offending_pdu_len={}",
            self.version,
            offending_pdu.len()
        );
        let (data_available, payloads, session_id, serial, timing) = {
            let version = self.version()?;
            let cache = self.cache.load_full();
            let data_available = cache.is_data_available();
            let payloads = cache.rtr_payloads_for_version(version);
            let session_id = cache.session_id_for_version(version);
            let serial = cache.serial_for_version(version);
            let timing = cache.timing();
            (data_available, payloads, session_id, serial, timing)
        };

        if !data_available {
            self.send_no_data_available(offending_pdu, "cache data is not currently available")
                .await?;
            debug!(
                "RTR session replied No Data Available to Reset Query: negotiated_version={:?}",
                self.version
            );
            return Ok(());
        }

        self.write_cache_response(session_id).await?;
        self.send_payloads(payloads.as_slice(), true).await?;
        self.write_end_of_data(session_id, serial, timing).await?;
        debug!(
            "RTR session completed Reset Query: response_session_id={}, response_serial={}, payload_count={}, state={}, negotiated_version={:?}",
            session_id,
            serial,
            payloads.len(),
            self.state_name(),
            self.version
        );

        Ok(())
    }

    async fn handle_serial(
        &mut self,
        version: u8,
        client_session: u16,
        client_serial: u32,
        offending_pdu: &[u8],
    ) -> Result<()> {
        debug!(
            "RTR session received Serial Query: negotiated_version={}, client_session_id={}, client_serial={}, offending_pdu_len={}",
            version,
            client_session,
            client_serial,
            offending_pdu.len()
        );
        let (data_available, current_session, current_serial, timing, serial_result) = {
            let cache = self.cache.load_full();
            (
                cache.is_data_available(),
                cache.session_id_for_version(version),
                cache.serial_for_version(version),
                cache.timing(),
                cache.get_deltas_since_for_version(version, client_serial),
            )
        };

        if !data_available {
            self.send_no_data_available(offending_pdu, "cache data is not currently available")
                .await?;
            debug!(
                "RTR session replied No Data Available to Serial Query: client_session_id={}, client_serial={}, state={}, negotiated_version={:?}",
                client_session,
                client_serial,
                self.state_name(),
                self.version
            );
            return Ok(());
        }

        // Strict 8210bis behavior:
        // within an established transport session, a Session ID mismatch is fatal.
        if client_session != current_session {
            self.send_corrupt_session_id(offending_pdu, client_session, current_session)
                .await?;
            self.state = SessionState::Closed;
            bail!(
                "session id mismatch in established session: received={}, expected={}",
                client_session,
                current_session
            );
        }

        match serial_result {
            SerialResult::ResetRequired => {
                self.write_cache_reset().await?;
                debug!(
                    "RTR session replied Cache Reset to Serial Query: client_session_id={}, client_serial={}, state={}, negotiated_version={:?}",
                    client_session,
                    client_serial,
                    self.state_name(),
                    self.version
                );
                return Ok(());
            }

            SerialResult::UpToDate => {
                self.write_cache_response(current_session).await?;
                self.write_end_of_data(current_session, current_serial, timing)
                    .await?;
                debug!(
                    "RTR session replied CacheResponse+EndOfData (up-to-date) to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, state={}, negotiated_version={:?}",
                    client_session,
                    client_serial,
                    current_session,
                    current_serial,
                    self.state_name(),
                    self.version
                );
                return Ok(());
            }

            SerialResult::Delta(delta) => {
                self.write_cache_response(current_session).await?;
                self.send_delta(&delta).await?;
                self.write_end_of_data(current_session, current_serial, timing)
                    .await?;
                debug!(
                    "RTR session replied delta to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, state={}, negotiated_version={:?}",
                    client_session,
                    client_serial,
                    current_session,
                    current_serial,
                    self.state_name(),
                    self.version
                );
            }
        }

        Ok(())
    }

    async fn handle_notify(&mut self) -> Result<()> {
        if self.state != SessionState::Established {
            return Ok(());
        }

        let version = match self.version {
            Some(v) => v,
            None => return Ok(()),
        };

        let now = Instant::now();
        if let Some(last) = self.last_notify_at {
            if now.duration_since(last) < NOTIFY_MIN_INTERVAL {
                info!(
                    "RTR session skipped SerialNotify due to rate limit: state={}, negotiated_version={:?}",
                    self.state_name(),
                    self.version
                );
                return Ok(());
            }
        }

        let (session_id, serial) = {
            let cache = self.cache.load_full();
            (
                cache.session_id_for_version(version),
                cache.serial_for_version(version),
            )
        };

        debug!(
            "RTR session sending SerialNotify: notify_session_id={}, notify_serial={}, state={}, negotiated_version={:?}",
            session_id,
            serial,
            self.state_name(),
            self.version
        );
        if let Err(err) = SerialNotify::new(version, session_id, serial)
            .write(&mut self.stream)
            .await
        {
            error_rate_limited("session_serial_notify_send_failed", || {
                format!(
                    "RTR session failed to send SerialNotify: err={}, notify_session_id={}, notify_serial={}, state={}, negotiated_version={:?}",
                    err,
                    session_id,
                    serial,
                    self.state_name(),
                    self.version
                )
            });
            return Err(err.into());
        }

        self.last_notify_at = Some(now);
        info!(
            "RTR session sent SerialNotify: notify_session_id={}, notify_serial={}, state={}, negotiated_version={:?}",
            session_id,
            serial,
            self.state_name(),
            self.version
        );
        Ok(())
    }

    fn session_summary(&self) -> String {
        let (session_id, serial) = if let Some(version) = self.version {
            let cache = self.cache.load_full();
            (
                cache.session_id_for_version(version).to_string(),
                cache.serial_for_version(version).to_string(),
            )
        } else {
            ("<unknown>".to_string(), "<unavailable>".to_string())
        };
        format!(
            "state={}, negotiated_version={:?}, cache_session_id={}, cache_serial={}",
            self.state_name(),
            self.version,
            session_id,
            serial
        )
    }

    fn state_name(&self) -> &'static str {
        match self.state {
            SessionState::Connected => "Connected",
            SessionState::Established => "Established",
            SessionState::Closed => "Closed",
        }
    }

    fn version(&self) -> io::Result<u8> {
        self.version
            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "version not negotiated"))
    }

    fn transport_timeout(&self) -> Duration {
        if let Some(timeout) = self.transport_timeout_override {
            return timeout;
        }

        let retry = self.cache.load_full().timing().retry();

        retry.checked_mul(3).unwrap_or(retry)
    }

    async fn write_cache_response(&mut self, session_id: u16) -> Result<()> {
        let version = self.version()?;
        debug!(
            "RTR session writing Cache Response: version={}, session_id={}",
            version, session_id
        );
        CacheResponse::new(version, session_id)
            .write(&mut self.stream)
            .await?;
        Ok(())
    }

    async fn write_cache_reset(&mut self) -> Result<()> {
        let version = self.version()?;
        info!("RTR session writing Cache Reset: version={}", version);
        CacheReset::new(version).write(&mut self.stream).await?;
        Ok(())
    }

    async fn write_end_of_data(&mut self, session_id: u16, serial: u32, timing: Timing) -> Result<()> {
        let version = self.version()?;

        let end = EndOfData::new(version, session_id, serial, timing)?;
        debug!(
            "RTR session writing EndOfData: version={}, session_id={}, serial={}, refresh={}s, retry={}s, expire={}s",
            version,
            session_id,
            serial,
            timing.refresh().as_secs(),
            timing.retry().as_secs(),
            timing.expire().as_secs()
        );
        match end {
            EndOfData::V0(pdu) => pdu.write(&mut self.stream).await?,
            EndOfData::V1(pdu) => pdu.write(&mut self.stream).await?,
        }

        Ok(())
    }

    async fn send_payloads(&mut self, payloads: &[Payload], announce: bool) -> Result<()> {
        // draft-ietf-sidrops-8210bis-25 Section 11.4 / 12 define Ordering Error
        // as a receiver-side response to PDUs received in the wrong order.
        // When we detect an ordering issue before sending, this is a local cache/
        // implementation fault rather than a received protocol error, so we fail
        // the session internally instead of emitting ErrorReport(code=11).
        // References:
        // https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-8210bis-25#section-11.4
        // https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-8210bis-25#section-12
        validate_payloads_for_rtr(payloads, announce).map_err(|err| anyhow!(err.to_string()))?;
        let version = self.version()?;
        let (route_origins, router_keys, aspas) = count_payloads(payloads);
        debug!(
            "RTR session sending snapshot payloads: announce={}, total={}, route_origins={}, router_keys={}, aspas={}",
            announce,
            payloads.len(),
            route_origins,
            router_keys,
            aspas
        );
        let mut writer = BufWriter::new(&mut self.stream);
        for payload in payloads {
            Self::send_payload_to(&mut writer, payload, announce, version).await?;
        }
        writer.flush().await?;
        Ok(())
    }

    async fn send_delta(&mut self, delta: &Delta) -> Result<()> {
        let updates = delta.payload_updates_for_rtr();
        // draft-ietf-sidrops-8210bis-25 Section 11.4 / 12 define Ordering Error
        // for the party receiving out-of-order PDUs. A validator failure here
        // means we are about to send an invalid sequence, so abort locally
        // instead of reporting Ordering Error back to the router.
        // References:
        // https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-8210bis-25#section-11.4
        // https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-8210bis-25#section-12
        validate_payload_update_refs_for_rtr(&updates).map_err(|err| anyhow!(err.to_string()))?;
        let version = self.version()?;
        let (announced, withdrawn, route_origins, router_keys, aspas) =
            count_payload_updates(&updates);
        debug!(
            "RTR session sending delta payloads: total={}, announced={}, withdrawn={}, route_origins={}, router_keys={}, aspas={}",
            updates.len(),
            announced,
            withdrawn,
            route_origins,
            router_keys,
            aspas
        );
        let mut writer = BufWriter::new(&mut self.stream);
        for (announce, payload) in &updates {
            Self::send_payload_to(&mut writer, payload, *announce, version).await?;
        }
        writer.flush().await?;
        Ok(())
    }

    async fn send_payload_to<W>(
        writer: &mut W,
        payload: &Payload,
        announce: bool,
        version: u8,
    ) -> Result<()>
    where
        W: AsyncWrite + Unpin,
    {
        match payload {
            Payload::RouteOrigin(origin) => {
                Self::send_route_origin_to(writer, origin, announce, version).await?;
            }
            Payload::RouterKey(key) => {
                if version >= 1 {
                    Self::send_router_key_to(writer, key, announce, version).await?;
                }
            }
            Payload::Aspa(aspa) => {
                if version >= 2 {
                    Self::send_aspa_to(writer, aspa, announce, version).await?;
                }
            }
        }
        Ok(())
    }

    async fn send_route_origin_to<W>(
        writer: &mut W,
        origin: &RouteOrigin,
        announce: bool,
        version: u8,
    ) -> Result<()>
    where
        W: AsyncWrite + Unpin,
    {
        let flags = Flags::new(if announce {
            ANNOUNCE_FLAG
        } else {
            WITHDRAW_FLAG
        });

        let prefix = origin.prefix();
        let prefix_len = prefix.prefix_length;
        let max_len = origin.max_length();

        match prefix.address {
            IPAddress::V4(v4) => {
                IPv4Prefix::new(version, flags, prefix_len, max_len, v4, origin.asn())
                    .write(writer)
                    .await?;
            }
            IPAddress::V6(v6) => {
                IPv6Prefix::new(version, flags, prefix_len, max_len, v6, origin.asn())
                    .write(writer)
                    .await?;
            }
        }

        Ok(())
    }

    async fn send_router_key_to<W>(
        writer: &mut W,
        key: &RouterKey,
        announce: bool,
        version: u8,
    ) -> Result<()>
    where
        W: AsyncWrite + Unpin,
    {
        key.validate()?;

        let flags = Flags::new(if announce {
            ANNOUNCE_FLAG
        } else {
            WITHDRAW_FLAG
        });

        let pdu = RouterKeyPdu::new(
            version,
            flags,
            key.ski(),
            key.asn(),
            Arc::<[u8]>::from(key.spki().to_vec()),
        );

        pdu.write(writer).await?;
        Ok(())
    }

    async fn send_aspa_to<W>(
        writer: &mut W,
        aspa: &Aspa,
        announce: bool,
        version: u8,
    ) -> Result<()>
    where
        W: AsyncWrite + Unpin,
    {
        if announce {
            aspa.validate_announcement()?;
        }

        let flags = Flags::new(if announce {
            ANNOUNCE_FLAG
        } else {
            WITHDRAW_FLAG
        });

        let providers = if announce {
            aspa.provider_asns()
                .iter()
                .map(|asn| asn.into_u32())
                .collect::<Vec<_>>()
        } else {
            Vec::new()
        };

        let pdu = AspaPdu::new(version, flags, aspa.customer_asn().into_u32(), providers);

        pdu.write(writer).await?;
        Ok(())
    }

    async fn send_error(
        &mut self,
        version: u8,
        code: ErrorCode,
        offending_pdu: &[u8],
        text: &[u8],
    ) -> io::Result<()> {
        let text_preview = String::from_utf8_lossy(text);
        warn_rate_limited("session_error_report_sent", || {
            format!(
                "RTR session sending ErrorReport: version={}, error_code={}({}), offending_pdu_len={}, text={}",
                version,
                code.as_u16(),
                code.description(),
                offending_pdu.len(),
                text_preview
            )
        });
        ErrorReport::new(version, code.as_u16(), offending_pdu, text)
            .write(&mut self.stream)
            .await
    }

    async fn handle_pdu_read_error(&mut self, header: Header, err: io::Error) -> Result<()> {
        warn_rate_limited("session_established_pdu_read_failed", || {
            format!(
                "RTR session failed to read established-session PDU payload: pdu={}, version={}, err={}",
                header.pdu(),
                header.version(),
                err
            )
        });
        if err.kind() == io::ErrorKind::InvalidData {
            let offending = self.read_full_pdu_bytes(header).await?;
            let version = self.version()?;
            let detail = err.to_string();
            let _ = self
                .send_corrupt_data(version, &offending, detail.as_bytes())
                .await;
        }

        self.state = SessionState::Closed;
        Ok(())
    }

    async fn handle_first_pdu_read_error(&mut self, header: Header, err: io::Error) -> Result<()> {
        warn_rate_limited("session_first_pdu_read_failed", || {
            format!(
                "RTR session failed to read first PDU payload: pdu={}, version={}, err={}",
                header.pdu(),
                header.version(),
                err
            )
        });
        if err.kind() == io::ErrorKind::InvalidData {
            let offending = self.read_full_pdu_bytes(header).await?;
            let err_version =
                if (SUPPORTED_MIN_VERSION..=SUPPORTED_MAX_VERSION).contains(&header.version()) {
                    header.version()
                } else {
                    SUPPORTED_MAX_VERSION
                };
            let detail = err.to_string();

            let _ = self
                .send_corrupt_data(err_version, &offending, detail.as_bytes())
                .await;
        }

        self.state = SessionState::Closed;
        Ok(())
    }

    async fn handle_header_read_error(
        &mut self,
        raw_header: [u8; HEADER_LEN],
        err: io::Error,
    ) -> Result<()> {
        warn_rate_limited("session_invalid_header_bytes", || {
            format!(
                "RTR session handling invalid header bytes: raw_header={:02X?}, err={}",
                raw_header, err
            )
        });
        if err.kind() == io::ErrorKind::InvalidData {
            let version = match self.version {
                Some(version) => version,
                None if (SUPPORTED_MIN_VERSION..=SUPPORTED_MAX_VERSION)
                    .contains(&raw_header[0]) =>
                {
                    raw_header[0]
                }
                None => SUPPORTED_MAX_VERSION,
            };
            let detail = err.to_string();

            let _ = self
                .send_corrupt_data(version, &raw_header, detail.as_bytes())
                .await;
        }

        self.state = SessionState::Closed;
        Ok(())
    }

    async fn handle_transport_timeout(&mut self, offending_pdu: &[u8]) -> Result<()> {
        let version = self.version.unwrap_or(SUPPORTED_MAX_VERSION);
        let timeout = self.transport_timeout();
        let detail = format!("transport stalled for longer than {:?}", timeout);
        warn_rate_limited("session_transport_timeout", || {
            format!(
                "RTR session transport timeout: version={}, offending_pdu_len={}, timeout={:?}",
                version,
                offending_pdu.len(),
                timeout
            )
        });

        let _ = self
            .send_error(
                version,
                ErrorCode::TransportFailed,
                offending_pdu,
                detail.as_bytes(),
            )
            .await;

        self.state = SessionState::Closed;
        Ok(())
    }

    async fn read_full_pdu_bytes(&mut self, header: Header) -> io::Result<Vec<u8>> {
        let total_len = header.pdu_len()?;
        let Some(payload_len) = total_len.checked_sub(HEADER_LEN) else {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "PDU size smaller than header size",
            ));
        };

        let capture_len = total_len.min(MAX_OFFENDING_PDU_CAPTURE);
        let capture_payload_len = capture_len.saturating_sub(HEADER_LEN);
        let mut bytes = Vec::with_capacity(capture_len);
        bytes.extend_from_slice(header.as_ref());
        bytes.resize(capture_len, 0);
        if capture_payload_len > 0 {
            timeout(
                self.transport_timeout(),
                self.stream
                    .read_exact(&mut bytes[HEADER_LEN..HEADER_LEN + capture_payload_len]),
            )
            .await
            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "transport read timed out"))??;
        }

        let mut remaining = payload_len.saturating_sub(capture_payload_len);
        let mut discard_buf = [0u8; 1024];
        while remaining > 0 {
            let read_len = remaining.min(discard_buf.len());
            let read = timeout(
                self.transport_timeout(),
                self.stream.read(&mut discard_buf[..read_len]),
            )
            .await
            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "transport read timed out"))??;
            if read == 0 {
                return Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    "unexpected EOF while draining oversized PDU payload",
                ));
            }
            remaining -= read;
        }
        Ok(bytes)
    }
}

fn count_payloads(payloads: &[Payload]) -> (usize, usize, usize) {
    let mut route_origins = 0;
    let mut router_keys = 0;
    let mut aspas = 0;

    for payload in payloads {
        match payload {
            Payload::RouteOrigin(_) => route_origins += 1,
            Payload::RouterKey(_) => router_keys += 1,
            Payload::Aspa(_) => aspas += 1,
        }
    }

    (route_origins, router_keys, aspas)
}

fn count_payload_updates(updates: &[(bool, &Payload)]) -> (usize, usize, usize, usize, usize) {
    let mut announced = 0;
    let mut withdrawn = 0;
    let mut route_origins = 0;
    let mut router_keys = 0;
    let mut aspas = 0;

    for (announce, payload) in updates {
        if *announce {
            announced += 1;
        } else {
            withdrawn += 1;
        }

        match payload {
            Payload::RouteOrigin(_) => route_origins += 1,
            Payload::RouterKey(_) => router_keys += 1,
            Payload::Aspa(_) => aspas += 1,
        }
    }

    (announced, withdrawn, route_origins, router_keys, aspas)
}
