//! Routing error-signal wire PDUs.
//!
//! Plaintext link-layer error signals emitted by a transit router that
//! cannot forward a `SessionDatagram`: `CoordsRequired` (coordinate cache
//! miss), `PathBroken` (routing failure / local minimum), and `MtuExceeded`
//! (next-hop transport MTU too small). Their `0x20`–`0x2F` discriminants form
//! the routing-owned [`RoutingSignalType`] registry, split off from the FSP
//! `SessionMessageType` (which keeps the encrypted-inner `0x10`–`0x1F` range).
//! The shared address-only coordinate helpers are imported downward from
//! `crate::proto::stp`, and [`Error`] from `crate::proto` (both
//! allowed `proto -> proto` dependencies).

use crate::NodeAddr;
use crate::proto::Error;
use crate::proto::codec::{Reader, Writer};
use crate::proto::stp::{
    TreeCoordinate, decode_optional_coords, encode_coords, encode_empty_coords,
};
use core::fmt;

/// Plaintext link-layer routing error-signal message types (`0x20`–`0x2F`).
///
/// Emitted by transit routers that cannot forward a `SessionDatagram` and have
/// no end-to-end session with the source, so they are carried on the FSP
/// cleartext (U-flag) path rather than the AEAD-encrypted inner path. Split
/// from the FSP `SessionMessageType` (encrypted-inner `0x10`–`0x1F`): the two
/// decode sites are partitioned by byte-range and crypto context, so neither
/// registry references the other's range.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum RoutingSignalType {
    /// Router cache miss — needs coordinates.
    CoordsRequired = 0x20,
    /// Routing failure — local minimum or unreachable.
    PathBroken = 0x21,
    /// MTU exceeded — forwarded packet too large for next-hop transport.
    MtuExceeded = 0x22,
}

impl RoutingSignalType {
    /// Try to convert from a byte.
    pub fn from_byte(b: u8) -> Option<Self> {
        match b {
            0x20 => Some(RoutingSignalType::CoordsRequired),
            0x21 => Some(RoutingSignalType::PathBroken),
            0x22 => Some(RoutingSignalType::MtuExceeded),
            _ => None,
        }
    }

    /// Convert to a byte.
    pub fn to_byte(self) -> u8 {
        self as u8
    }
}

impl fmt::Display for RoutingSignalType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            RoutingSignalType::CoordsRequired => "CoordsRequired",
            RoutingSignalType::PathBroken => "PathBroken",
            RoutingSignalType::MtuExceeded => "MtuExceeded",
        };
        write!(f, "{}", name)
    }
}

/// Link-layer error signal indicating router cache miss.
///
/// Generated by a transit router when it cannot forward a SessionDatagram
/// due to missing cached coordinates for the destination. Carried inside
/// a new SessionDatagram addressed back to the original source
/// (src_addr=reporter, dest_addr=original_source). Plaintext — not
/// end-to-end encrypted, since the transit router has no session with
/// the source.
///
/// ## Wire Format
///
/// | Offset | Field    | Size     | Description                        |
/// |--------|----------|---------|------------------------------------|
/// | 0      | msg_type | 1 byte  | 0x20                               |
/// | 1      | flags    | 1 byte  | Reserved                           |
/// | 2      | dest_addr| 16 bytes| The node_addr we couldn't route to |
/// | 18     | reporter | 16 bytes| NodeAddr of reporting router       |
///
/// Payload: 34 bytes
#[derive(Clone, Debug)]
pub struct CoordsRequired {
    /// Destination that couldn't be routed.
    pub dest_addr: NodeAddr,
    /// Router reporting the miss.
    pub reporter: NodeAddr,
}

/// Wire size of CoordsRequired payload: msg_type(1) + flags(1) + dest_addr(16) + reporter(16).
pub const COORDS_REQUIRED_SIZE: usize = 34;

impl CoordsRequired {
    /// Create a new CoordsRequired error.
    pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
        Self {
            dest_addr,
            reporter,
        }
    }

    /// Encode as wire format (4-byte FSP prefix + msg_type + body).
    ///
    /// Error signals use phase=0x0 with U flag set.
    pub fn encode(&self) -> Vec<u8> {
        // Body: msg_type + flags(reserved) + dest_addr + reporter
        let body_len = 1 + 1 + 16 + 16; // 34 bytes
        let mut w = Writer::with_capacity(4 + body_len);
        // FSP prefix: version 0, phase 0x0, U flag set
        w.write_u8(0x00); // version 0, phase 0x0
        w.write_u8(0x04); // U flag
        let payload_len = body_len as u16;
        w.write_u16_le(payload_len);
        // msg_type byte (after prefix, before body)
        w.write_u8(RoutingSignalType::CoordsRequired.to_byte());
        w.write_u8(0x00); // reserved flags
        w.write_bytes(self.dest_addr.as_bytes());
        w.write_bytes(self.reporter.as_bytes());
        w.into_vec()
    }

    /// Decode from wire format (after FSP prefix and msg_type byte consumed).
    pub fn decode(payload: &[u8]) -> Result<Self, Error> {
        // flags(1) + dest_addr(16) + reporter(16) = 33
        let mut reader = Reader::new(payload);
        reader.require(33)?;
        // payload[0] is flags (reserved, ignored)
        reader.advance(1);
        let dest_bytes = reader.read_array::<16>()?;
        let reporter_bytes = reader.read_array::<16>()?;

        Ok(Self {
            dest_addr: NodeAddr::from_bytes(dest_bytes),
            reporter: NodeAddr::from_bytes(reporter_bytes),
        })
    }
}

/// Error indicating routing failure (local minimum or unreachable).
///
/// Carried inside a SessionDatagram addressed back to the original source.
/// The reporting router creates a new SessionDatagram with src_addr=reporter
/// and dest_addr=original_source, so the `original_src` field from the old
/// design is no longer needed — it's the SessionDatagram's dest_addr.
///
/// ## Wire Format
///
/// | Offset | Field             | Size     | Description                   |
/// |--------|-------------------|----------|-------------------------------|
/// | 0      | msg_type          | 1 byte   | 0x21                          |
/// | 1      | flags             | 1 byte   | Reserved                      |
/// | 2      | dest_addr         | 16 bytes | The unreachable node_addr     |
/// | 18     | reporter          | 16 bytes | NodeAddr of reporting router   |
/// | 34     | last_coords_count | 2 bytes  | u16 LE                        |
/// | 36     | last_known_coords | 16 × n   | Stale coords that failed      |
#[derive(Clone, Debug)]
pub struct PathBroken {
    /// Destination that couldn't be reached.
    pub dest_addr: NodeAddr,
    /// Node that detected the failure.
    pub reporter: NodeAddr,
    /// Optional: last known coordinates of destination.
    pub last_known_coords: Option<TreeCoordinate>,
}

impl PathBroken {
    /// Create a new PathBroken error.
    pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
        Self {
            dest_addr,
            reporter,
            last_known_coords: None,
        }
    }

    /// Add last known coordinates.
    pub fn with_last_coords(mut self, coords: TreeCoordinate) -> Self {
        self.last_known_coords = Some(coords);
        self
    }

    /// Encode as wire format (4-byte FSP prefix + msg_type + body).
    ///
    /// Error signals use phase=0x0 with U flag set.
    pub fn encode(&self) -> Vec<u8> {
        // Build body first to compute length
        let mut body = Vec::new();
        body.push(RoutingSignalType::PathBroken.to_byte());
        body.push(0x00); // reserved flags
        body.extend_from_slice(self.dest_addr.as_bytes());
        body.extend_from_slice(self.reporter.as_bytes());
        if let Some(ref coords) = self.last_known_coords {
            encode_coords(coords, &mut body);
        } else {
            encode_empty_coords(&mut body);
        }

        // Prepend FSP prefix: version 0, phase 0x0, U flag set
        let payload_len = body.len() as u16;
        let mut buf = Vec::with_capacity(4 + body.len());
        buf.push(0x00); // version 0, phase 0x0
        buf.push(0x04); // U flag
        buf.extend_from_slice(&payload_len.to_le_bytes());
        buf.extend_from_slice(&body);
        buf
    }

    /// Decode from wire format (after FSP prefix and msg_type byte consumed).
    pub fn decode(payload: &[u8]) -> Result<Self, Error> {
        // flags(1) + dest_addr(16) + reporter(16) + coords_count(2) = 35 minimum
        let mut reader = Reader::new(payload);
        reader.require(35)?;
        // payload[0] is flags (reserved, ignored)
        reader.advance(1);
        let dest_bytes = reader.read_array::<16>()?;
        let reporter_bytes = reader.read_array::<16>()?;

        let (last_known_coords, _consumed) = decode_optional_coords(reader.rest())?;

        Ok(Self {
            dest_addr: NodeAddr::from_bytes(dest_bytes),
            reporter: NodeAddr::from_bytes(reporter_bytes),
            last_known_coords,
        })
    }
}

/// Error indicating a forwarded packet exceeded the next-hop transport MTU.
///
/// Generated by a transit router when `send_encrypted_link_message()`
/// fails with `TransportError::MtuExceeded`. The reporter includes the
/// bottleneck MTU so the source can immediately reduce its sending MTU.
///
/// ## Wire Format
///
/// | Offset | Field     | Size     | Description                        |
/// |--------|-----------|----------|------------------------------------|
/// | 0      | msg_type  | 1 byte   | 0x22                               |
/// | 1      | flags     | 1 byte   | Reserved                           |
/// | 2      | dest_addr | 16 bytes | The destination we were forwarding |
/// | 18     | reporter  | 16 bytes | NodeAddr of reporting router       |
/// | 34     | mtu       | 2 bytes  | Bottleneck MTU (u16 LE)            |
///
/// Payload: 36 bytes
#[derive(Clone, Debug)]
pub struct MtuExceeded {
    /// Destination that the oversized packet was heading to.
    pub dest_addr: NodeAddr,
    /// Router that detected the MTU violation.
    pub reporter: NodeAddr,
    /// Transport MTU at the bottleneck hop.
    pub mtu: u16,
}

/// Wire size of MtuExceeded payload: msg_type(1) + flags(1) + dest_addr(16) + reporter(16) + mtu(2).
pub const MTU_EXCEEDED_SIZE: usize = 36;

impl MtuExceeded {
    /// Create a new MtuExceeded error.
    pub fn new(dest_addr: NodeAddr, reporter: NodeAddr, mtu: u16) -> Self {
        Self {
            dest_addr,
            reporter,
            mtu,
        }
    }

    /// Encode as wire format (4-byte FSP prefix + msg_type + body).
    ///
    /// Error signals use phase=0x0 with U flag set.
    pub fn encode(&self) -> Vec<u8> {
        let body_len = MTU_EXCEEDED_SIZE; // 36 bytes
        let mut w = Writer::with_capacity(4 + body_len);
        // FSP prefix: version 0, phase 0x0, U flag set
        w.write_u8(0x00); // version 0, phase 0x0
        w.write_u8(0x04); // U flag
        let payload_len = body_len as u16;
        w.write_u16_le(payload_len);
        // msg_type byte
        w.write_u8(RoutingSignalType::MtuExceeded.to_byte());
        w.write_u8(0x00); // reserved flags
        w.write_bytes(self.dest_addr.as_bytes());
        w.write_bytes(self.reporter.as_bytes());
        w.write_u16_le(self.mtu);
        w.into_vec()
    }

    /// Decode from wire format (after FSP prefix and msg_type byte consumed).
    pub fn decode(payload: &[u8]) -> Result<Self, Error> {
        // flags(1) + dest_addr(16) + reporter(16) + mtu(2) = 35
        let mut reader = Reader::new(payload);
        reader.require(35)?;
        // payload[0] is flags (reserved, ignored)
        reader.advance(1);
        let dest_bytes = reader.read_array::<16>()?;
        let reporter_bytes = reader.read_array::<16>()?;
        let mtu = reader.read_u16_le()?;

        Ok(Self {
            dest_addr: NodeAddr::from_bytes(dest_bytes),
            reporter: NodeAddr::from_bytes(reporter_bytes),
            mtu,
        })
    }
}
