use crate::data_model::common::{DerReader, der_take_tlv};
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
use crate::data_model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
use crate::data_model::signed_object::{
    RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
    SignedObjectValidateError,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaObject {
    pub signed_object: RpkiSignedObject,
    pub econtent_type: String,
    pub roa: RoaEContent,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaObjectParsed {
    pub signed_object: RpkiSignedObjectParsed,
    pub econtent_type: String,
    pub roa: Option<RoaEContentParsed>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaEContent {
    pub version: u32,
    pub as_id: u32,
    pub ip_addr_blocks: Vec<RoaIpAddressFamily>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaEContentParsed {
    der: Vec<u8>,
}

#[derive(Debug, thiserror::Error)]
pub enum RoaParseError {
    #[error("signed object parse error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")]
    SignedObject(#[from] SignedObjectParseError),
    #[error("ROA parse error: {0} (RFC 9582 §4; DER)")]
    Parse(String),

    #[error("ROA trailing bytes: {0} bytes (RFC 9582 §4; DER)")]
    TrailingBytes(usize),
}

#[derive(Debug, thiserror::Error)]
pub enum RoaProfileError {
    #[error("signed object profile error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")]
    SignedObject(#[from] SignedObjectValidateError),

    #[error("ROA eContentType must be {OID_CT_ROUTE_ORIGIN_AUTHZ}, got {0} (RFC 9582 §3)")]
    InvalidEContentType(String),

    #[error("ROA profile decode error: {0} (RFC 9582 §4; DER)")]
    ProfileDecode(String),

    #[error("RouteOriginAttestation must be a SEQUENCE of 2 or 3 elements, got {0} (RFC 9582 §4)")]
    InvalidAttestationSequenceLen(usize),

    #[error("ROA version must be 0, got {0} (RFC 9582 §4.1)")]
    InvalidVersion(u64),

    #[error("ROA asID out of range (0..=4294967295), got {0} (RFC 9582 §4.2)")]
    AsIdOutOfRange(u64),

    #[error("ROA ipAddrBlocks must have length 1..2, got {0} (RFC 9582 §4; RFC 9582 §4.3.1)")]
    InvalidIpAddrBlocksLen(usize),

    #[error("ROAIPAddressFamily must be a SEQUENCE of 2 elements (RFC 9582 §4.3.1)")]
    InvalidIpAddressFamily,

    #[error("ROA addressFamily must be an OCTET STRING of 2 bytes (RFC 9582 §4.3.1)")]
    InvalidAddressFamily,

    #[error("ROA addressFamily AFI not supported: {0:02X?} (RFC 9582 §4.3.1)")]
    UnsupportedAfi(Vec<u8>),

    #[error("ROA contains duplicate AFI {0:?} (RFC 9582 §4.3.1)")]
    DuplicateAfi(RoaAfi),

    #[error("ROAAddresses must have at least one entry (RFC 9582 §4.3.2)")]
    EmptyAddressList,

    #[error("ROAIPAddress must be a SEQUENCE of 1..2 elements (RFC 9582 §4.3.2)")]
    InvalidRoaIpAddress,

    #[error("ROAIPAddress.address must be a BIT STRING (RFC 9582 §4.3.2.1; RFC 3779 §2.2.3.8)")]
    InvalidPrefixBitString,

    #[error(
        "ROAIPAddress.address has invalid unused bits encoding (RFC 9582 §4.3.2.1; RFC 3779 §2.2.3.8)"
    )]
    InvalidPrefixUnusedBits,

    #[error(
        "ROAIPAddress.address prefix length {prefix_len} out of range for {afi:?} (RFC 9582 §4.3.2.1)"
    )]
    PrefixLenOutOfRange { afi: RoaAfi, prefix_len: u16 },

    #[error(
        "ROAIPAddress.maxLength out of range for {afi:?}: prefix_len={prefix_len}, max_len={max_len} (RFC 9582 §4.3.2.2)"
    )]
    InvalidMaxLength {
        afi: RoaAfi,
        prefix_len: u16,
        max_len: u16,
    },
}

impl From<SignedObjectDecodeError> for RoaProfileError {
    fn from(value: SignedObjectDecodeError) -> Self {
        match value {
            SignedObjectDecodeError::Parse(e) => RoaProfileError::ProfileDecode(e.to_string()),
            SignedObjectDecodeError::Validate(e) => RoaProfileError::SignedObject(e),
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum RoaDecodeError {
    #[error("{0}")]
    Parse(#[from] RoaParseError),

    #[error("{0}")]
    Validate(#[from] RoaProfileError),
}

#[derive(Debug, thiserror::Error)]
pub enum RoaValidateError {
    #[error("ROA EE certificate must not contain AS resources extension (RFC 9582 §5)")]
    EeAsResourcesPresent,

    #[error("ROA EE certificate must contain IP resources extension (RFC 9582 §5)")]
    EeIpResourcesMissing,

    #[error("ROA EE certificate IP resources must not use inherit (RFC 9582 §5)")]
    EeIpResourcesInherit,

    #[error(
        "ROA prefix not covered by EE certificate IP resources: {afi:?} {addr:?}/{prefix_len} (RFC 9582 §5; RFC 3779 §2.3)"
    )]
    PrefixNotInEeResources {
        afi: RoaAfi,
        addr: Vec<u8>,
        prefix_len: u16,
    },
}

impl RoaObject {
    /// Parse step of scheme A (`parse → validate → verify`).
    pub fn parse_der(der: &[u8]) -> Result<RoaObjectParsed, RoaParseError> {
        let signed_object = RpkiSignedObject::parse_der(der)?;
        let econtent_type = signed_object
            .signed_data
            .encap_content_info
            .econtent_type
            .clone();
        let roa = signed_object
            .signed_data
            .encap_content_info
            .econtent
            .as_deref()
            .map(RoaEContent::parse_der)
            .transpose()?;
        Ok(RoaObjectParsed {
            signed_object,
            econtent_type,
            roa,
        })
    }

    /// Profile validate step of scheme A (`parse → validate → verify`).
    ///
    /// `RoaObject` is already profile-validated when constructed via `decode_der()` /
    /// `RoaObjectParsed::validate_profile()`.
    pub fn validate_profile(&self) -> Result<(), RoaProfileError> {
        Ok(())
    }

    pub fn decode_der(der: &[u8]) -> Result<Self, RoaDecodeError> {
        Ok(Self::parse_der(der)?.validate_profile()?)
    }

    pub fn decode_der_with_strict_options(
        der: &[u8],
        strict_cms_der: bool,
        strict_name: bool,
    ) -> Result<Self, RoaDecodeError> {
        let signed_object =
            RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
                .map_err(RoaProfileError::from)?;
        Self::from_signed_object(signed_object)
    }

    pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, RoaDecodeError> {
        let econtent_type = signed_object
            .signed_data
            .encap_content_info
            .econtent_type
            .clone();
        if econtent_type != OID_CT_ROUTE_ORIGIN_AUTHZ {
            return Err(RoaProfileError::InvalidEContentType(econtent_type).into());
        }

        let roa = RoaEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?;
        Ok(Self {
            roa,
            signed_object,
            econtent_type: OID_CT_ROUTE_ORIGIN_AUTHZ.to_string(),
        })
    }

    /// Validate this ROA's embedded EE certificate resources.
    pub fn validate_embedded_ee_cert(&self) -> Result<(), RoaValidateError> {
        let ee = &self.signed_object.signed_data.certificates[0].resource_cert;
        self.roa.validate_against_ee_cert(ee)
    }
}

#[derive(
    Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum RoaAfi {
    Ipv4,
    Ipv6,
}

impl RoaAfi {
    fn ub(self) -> u16 {
        match self {
            RoaAfi::Ipv4 => 32,
            RoaAfi::Ipv6 => 128,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaIpAddressFamily {
    pub afi: RoaAfi,
    pub addresses: Vec<RoaIpAddress>,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RoaIpAddress {
    pub prefix: IpPrefix,
    pub max_length: Option<u16>,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct IpPrefix {
    pub afi: RoaAfi,
    /// Prefix length in bits.
    pub prefix_len: u16,
    /// Network order address bytes (always 16 bytes), with host bits cleared.
    ///
    /// For IPv4 prefixes, only the first 4 bytes are used and the remaining 12 bytes are zero.
    pub addr: [u8; 16],
}

impl IpPrefix {
    pub fn addr_bytes(&self) -> &[u8] {
        match self.afi {
            RoaAfi::Ipv4 => &self.addr[..4],
            RoaAfi::Ipv6 => &self.addr[..16],
        }
    }
}

impl RoaEContent {
    /// Parse step of scheme A (`parse → validate → verify`).
    pub fn parse_der(der: &[u8]) -> Result<RoaEContentParsed, RoaParseError> {
        let (_tag, _value, rem) = der_take_tlv(der).map_err(RoaParseError::Parse)?;
        if !rem.is_empty() {
            return Err(RoaParseError::TrailingBytes(rem.len()));
        }
        Ok(RoaEContentParsed { der: der.to_vec() })
    }

    /// Profile validate step of scheme A (`parse → validate → verify`).
    ///
    /// `RoaEContent` is already profile-validated when constructed via `decode_der()` /
    /// `RoaEContentParsed::validate_profile()`.
    pub fn validate_profile(&self) -> Result<(), RoaProfileError> {
        Ok(())
    }

    /// Decode the DER-encoded RouteOriginAttestation defined in RFC 9582 §4 (`parse + validate`).
    pub fn decode_der(der: &[u8]) -> Result<Self, RoaDecodeError> {
        Ok(Self::parse_der(der)?.validate_profile()?)
    }

    pub fn canonicalize(&mut self) {
        self.ip_addr_blocks.sort_by_key(|f| f.afi);
        for fam in &mut self.ip_addr_blocks {
            fam.addresses.sort();
            fam.addresses.dedup();
        }
    }

    /// Validate ROA payload against the embedded EE resource certificate (RFC 9582 §5).
    ///
    /// This performs the EE/payload semantic checks that do not require certificate path
    /// validation.
    pub fn validate_against_ee_cert(
        &self,
        ee: &ResourceCertificate,
    ) -> Result<(), RoaValidateError> {
        if ee.tbs.extensions.as_resources.is_some() {
            return Err(RoaValidateError::EeAsResourcesPresent);
        }

        let ip = ee
            .tbs
            .extensions
            .ip_resources
            .as_ref()
            .ok_or(RoaValidateError::EeIpResourcesMissing)?;

        if ip.has_any_inherit() {
            return Err(RoaValidateError::EeIpResourcesInherit);
        }

        for fam in &self.ip_addr_blocks {
            for entry in &fam.addresses {
                let rc_prefix = roa_prefix_to_rc(&entry.prefix);
                if !ip.contains_prefix(&rc_prefix) {
                    return Err(RoaValidateError::PrefixNotInEeResources {
                        afi: entry.prefix.afi,
                        addr: entry.prefix.addr_bytes().to_vec(),
                        prefix_len: entry.prefix.prefix_len,
                    });
                }
            }
        }
        Ok(())
    }
}

impl RoaObjectParsed {
    pub fn validate_profile(self) -> Result<RoaObject, RoaProfileError> {
        let signed_object = self.signed_object.validate_profile()?;
        let econtent_type = signed_object
            .signed_data
            .encap_content_info
            .econtent_type
            .clone();
        if econtent_type != OID_CT_ROUTE_ORIGIN_AUTHZ {
            return Err(RoaProfileError::InvalidEContentType(econtent_type));
        }
        let roa = self
            .roa
            .ok_or_else(|| RoaProfileError::ProfileDecode("ROA.eContent missing".into()))?
            .validate_profile()?;
        Ok(RoaObject {
            signed_object,
            econtent_type: OID_CT_ROUTE_ORIGIN_AUTHZ.to_string(),
            roa,
        })
    }
}

impl RoaEContentParsed {
    pub fn validate_profile(self) -> Result<RoaEContent, RoaProfileError> {
        fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
            let mut n = 0usize;
            while !r.is_empty() {
                r.skip_any()?;
                n += 1;
            }
            Ok(n)
        }

        let mut r = DerReader::new(&self.der);
        let mut seq = r
            .take_sequence()
            .map_err(|e| RoaProfileError::ProfileDecode(e))?;
        if !r.is_empty() {
            return Err(RoaProfileError::ProfileDecode(
                "trailing bytes after RouteOriginAttestation".into(),
            ));
        }

        let elem_count =
            count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
        if elem_count != 2 && elem_count != 3 {
            return Err(RoaProfileError::InvalidAttestationSequenceLen(elem_count));
        }

        let mut version: u32 = 0;
        if elem_count == 3 {
            if seq
                .peek_tag()
                .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?
                != 0xA0
            {
                return Err(RoaProfileError::ProfileDecode(
                    "RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
                ));
            }
            let (inner_tag, inner_val) = seq
                .take_explicit(0xA0)
                .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
            if inner_tag != 0x02 {
                return Err(RoaProfileError::ProfileDecode(
                    "RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
                ));
            }
            let v = crate::data_model::common::der_uint_from_bytes(inner_val)
                .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
            if v != 0 {
                return Err(RoaProfileError::InvalidVersion(v));
            }
            version = 0;
        }

        let as_id_u64 = seq
            .take_uint_u64()
            .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
        if as_id_u64 > u32::MAX as u64 {
            return Err(RoaProfileError::AsIdOutOfRange(as_id_u64));
        }
        let as_id = as_id_u64 as u32;
        let ip_addr_blocks = parse_ip_addr_blocks_cursor(
            seq.take_sequence()
                .map_err(|e| RoaProfileError::ProfileDecode(format!("ipAddrBlocks: {e}")))?,
        )?;

        if !seq.is_empty() {
            // Extra elements beyond the expected 2..3.
            let extra =
                count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
            return Err(RoaProfileError::InvalidAttestationSequenceLen(
                elem_count + extra,
            ));
        }

        let mut out = RoaEContent {
            version,
            as_id,
            ip_addr_blocks,
        };
        out.canonicalize();
        Ok(out)
    }
}

fn roa_prefix_to_rc(p: &IpPrefix) -> RcIpPrefix {
    let afi = match p.afi {
        RoaAfi::Ipv4 => RcAfi::Ipv4,
        RoaAfi::Ipv6 => RcAfi::Ipv6,
    };
    RcIpPrefix {
        afi,
        prefix_len: p.prefix_len,
        addr: p.addr_bytes().to_vec(),
    }
}

fn parse_ip_addr_blocks_cursor(
    mut seq: DerReader<'_>,
) -> Result<Vec<RoaIpAddressFamily>, RoaProfileError> {
    fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
        let mut n = 0usize;
        while !r.is_empty() {
            r.skip_any()?;
            n += 1;
        }
        Ok(n)
    }

    let fam_count =
        count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
    if fam_count == 0 || fam_count > 2 {
        return Err(RoaProfileError::InvalidIpAddrBlocksLen(fam_count));
    }

    let mut out: Vec<RoaIpAddressFamily> = Vec::with_capacity(fam_count);
    while !seq.is_empty() {
        let family = parse_ip_address_family_cursor(
            seq.take_sequence()
                .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?,
        )?;
        if out.iter().any(|f| f.afi == family.afi) {
            return Err(RoaProfileError::DuplicateAfi(family.afi));
        }
        out.push(family);
    }
    Ok(out)
}

fn parse_ip_address_family_cursor(
    mut fam: DerReader<'_>,
) -> Result<RoaIpAddressFamily, RoaProfileError> {
    let afi = {
        let bytes = fam
            .take_octet_string()
            .map_err(|_e| RoaProfileError::InvalidAddressFamily)?;
        if bytes.len() != 2 {
            return Err(RoaProfileError::InvalidAddressFamily);
        }
        match bytes {
            [0x00, 0x01] => RoaAfi::Ipv4,
            [0x00, 0x02] => RoaAfi::Ipv6,
            _ => return Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())),
        }
    };

    let mut addrs = fam
        .take_sequence()
        .map_err(|_e| RoaProfileError::InvalidIpAddressFamily)?;
    if !fam.is_empty() {
        return Err(RoaProfileError::InvalidIpAddressFamily);
    }

    if addrs.is_empty() {
        return Err(RoaProfileError::EmptyAddressList);
    }
    let mut addresses: Vec<RoaIpAddress> = Vec::new();
    while !addrs.is_empty() {
        let entry = addrs
            .take_sequence()
            .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
        addresses.push(parse_roa_ip_address_cursor(afi, entry)?);
    }

    Ok(RoaIpAddressFamily { afi, addresses })
}

fn parse_roa_ip_address_cursor(
    afi: RoaAfi,
    mut seq: DerReader<'_>,
) -> Result<RoaIpAddress, RoaProfileError> {
    if seq.is_empty() {
        return Err(RoaProfileError::InvalidRoaIpAddress);
    }

    let (unused_bits, bytes) = seq
        .take_bit_string()
        .map_err(|_e| RoaProfileError::InvalidPrefixBitString)?;
    let prefix = parse_prefix_bits_bytes(afi, unused_bits, bytes)?;

    let max_length = if !seq.is_empty() {
        let v = seq
            .take_uint_u64()
            .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
        let max_len: u16 = v
            .try_into()
            .map_err(|_e| RoaProfileError::InvalidMaxLength {
                afi,
                prefix_len: prefix.prefix_len,
                max_len: u16::MAX,
            })?;
        Some(max_len)
    } else {
        None
    };

    if !seq.is_empty() {
        return Err(RoaProfileError::InvalidRoaIpAddress);
    }

    if let Some(max_len) = max_length {
        let ub = afi.ub();
        if max_len > ub || max_len < prefix.prefix_len {
            return Err(RoaProfileError::InvalidMaxLength {
                afi,
                prefix_len: prefix.prefix_len,
                max_len,
            });
        }
    }

    Ok(RoaIpAddress { prefix, max_length })
}

fn parse_prefix_bits_bytes(
    afi: RoaAfi,
    unused_bits: u8,
    bytes: &[u8],
) -> Result<IpPrefix, RoaProfileError> {
    if unused_bits > 7 {
        return Err(RoaProfileError::InvalidPrefixUnusedBits);
    }
    if bytes.is_empty() {
        if unused_bits != 0 {
            return Err(RoaProfileError::InvalidPrefixUnusedBits);
        }
    } else if unused_bits != 0 {
        let mask = (1u8 << unused_bits) - 1;
        if (bytes[bytes.len() - 1] & mask) != 0 {
            return Err(RoaProfileError::InvalidPrefixUnusedBits);
        }
    }

    let prefix_len = (bytes.len() * 8)
        .checked_sub(unused_bits as usize)
        .ok_or(RoaProfileError::InvalidPrefixUnusedBits)? as u16;
    if prefix_len > afi.ub() {
        return Err(RoaProfileError::PrefixLenOutOfRange { afi, prefix_len });
    }

    let addr = canonicalize_prefix_addr(afi, prefix_len, bytes);
    Ok(IpPrefix {
        afi,
        prefix_len,
        addr,
    })
}

fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> [u8; 16] {
    let full_len = match afi {
        RoaAfi::Ipv4 => 4,
        RoaAfi::Ipv6 => 16,
    };
    let mut addr = [0u8; 16];
    let copy_len = bytes.len().min(full_len);
    addr[..copy_len].copy_from_slice(&bytes[..copy_len]);

    if prefix_len == 0 {
        return addr;
    }

    let last_prefix_bit = (prefix_len - 1) as usize;
    let last_prefix_byte = last_prefix_bit / 8;
    let rem = (prefix_len % 8) as u8;
    if rem != 0 {
        let mask: u8 = 0xFF << (8 - rem);
        if last_prefix_byte < full_len {
            addr[last_prefix_byte] &= mask;
        }
    }
    addr
}
