use crate::data_model::common::{DerReader, der_take_tlv};
use crate::data_model::oid::OID_CT_ASPA;
use crate::data_model::rc::ResourceCertificate;
use crate::data_model::signed_object::{
    RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
    SignedObjectValidateError,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AspaObject {
    pub signed_object: RpkiSignedObject,
    pub econtent_type: String,
    pub aspa: AspaEContent,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AspaObjectParsed {
    pub signed_object: RpkiSignedObjectParsed,
    pub econtent_type: String,
    pub aspa: Option<AspaEContentParsed>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AspaEContent {
    pub version: u32,
    pub customer_as_id: u32,
    pub provider_as_ids: Vec<u32>,
}

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

#[derive(Debug, thiserror::Error)]
pub enum AspaParseError {
    #[error("signed object parse error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")]
    SignedObject(#[from] SignedObjectParseError),
    #[error("ASPA parse error: {0} (draft-ietf-sidrops-aspa-profile-21 §3; DER)")]
    Parse(String),

    #[error("ASPA trailing bytes: {0} bytes (draft-ietf-sidrops-aspa-profile-21 §3; DER)")]
    TrailingBytes(usize),
}

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

    #[error(
        "ASPA eContentType must be {OID_CT_ASPA}, got {0} (draft-ietf-sidrops-aspa-profile-21 §2)"
    )]
    InvalidEContentType(String),

    #[error("ASPA profile decode error: {0} (draft-ietf-sidrops-aspa-profile-21 §3; DER)")]
    ProfileDecode(String),

    #[error(
        "ASProviderAttestation must be a SEQUENCE of 3 elements (draft-ietf-sidrops-aspa-profile-21 §3)"
    )]
    InvalidAttestationSequence,

    #[error(
        "ASPA version must be 1 and MUST be explicitly encoded (draft-ietf-sidrops-aspa-profile-21 §3.1)"
    )]
    VersionMustBeExplicitOne,

    #[error(
        "ASPA customerASID out of range (0..=4294967295), got {0} (draft-ietf-sidrops-aspa-profile-21 §3.2)"
    )]
    CustomerAsIdOutOfRange(u64),

    #[error(
        "ASPA providers must contain at least one ASID (draft-ietf-sidrops-aspa-profile-21 §3.3)"
    )]
    EmptyProviders,

    #[error(
        "ASPA provider ASID out of range (0..=4294967295), got {0} (draft-ietf-sidrops-aspa-profile-21 §3.3)"
    )]
    ProviderAsIdOutOfRange(u64),

    #[error(
        "ASPA providers must be in strictly increasing order (draft-ietf-sidrops-aspa-profile-21 §3.3)"
    )]
    ProvidersNotStrictlyIncreasing,

    #[error(
        "ASPA providers contains the customerASID ({0}) which is not allowed (draft-ietf-sidrops-aspa-profile-21 §3.3)"
    )]
    ProvidersContainCustomer(u32),
}

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

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

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

#[derive(Debug, thiserror::Error)]
pub enum AspaValidateError {
    #[error(
        "ASPA EE certificate must contain AS resources extension (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2)"
    )]
    EeAsResourcesMissing,

    #[error(
        "ASPA EE certificate AS resources must not use inherit (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.3)"
    )]
    EeAsResourcesInherit,

    #[error(
        "ASPA EE certificate AS resources must not include ranges (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.6-§3.2.3.7)"
    )]
    EeAsResourcesRangePresent,

    #[error(
        "ASPA EE certificate AS resources must not include RDI (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.5; RFC 6487 §4.8.11)"
    )]
    EeAsResourcesRdiPresent,

    #[error(
        "ASPA EE certificate AS resources must contain exactly one ASID (id element) (draft-ietf-sidrops-aspa-profile-21 §4)"
    )]
    EeAsResourcesNotSingleId,

    #[error(
        "ASPA customerASID ({customer_as_id}) does not match EE AS resources ({ee_as_id}) (draft-ietf-sidrops-aspa-profile-21 §4)"
    )]
    CustomerAsIdMismatch { customer_as_id: u32, ee_as_id: u32 },

    #[error(
        "ASPA EE certificate must not contain IP resources extension (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §2.2)"
    )]
    EeIpResourcesPresent,
}

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

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

    pub fn decode_der(der: &[u8]) -> Result<Self, AspaDecodeError> {
        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, AspaDecodeError> {
        let signed_object =
            RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
                .map_err(AspaProfileError::from)?;
        Self::from_signed_object(signed_object)
    }

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

        let aspa =
            AspaEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?;
        Ok(Self {
            aspa,
            signed_object,
            econtent_type: OID_CT_ASPA.to_string(),
        })
    }

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

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

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

    /// Decode the DER-encoded ASProviderAttestation defined in
    /// draft-ietf-sidrops-aspa-profile-21 §3 (`parse + validate`).
    pub fn decode_der(der: &[u8]) -> Result<Self, AspaDecodeError> {
        Ok(Self::parse_der(der)?.validate_profile()?)
    }

    /// Validate ASPA payload against the embedded EE resource certificate.
    ///
    /// This implements the EE/payload semantic checks described in
    /// `draft-ietf-sidrops-aspa-profile-21` §4 (as summarized in `rpki/specs/08_aspa.md`).
    pub fn validate_against_ee_cert(
        &self,
        ee: &ResourceCertificate,
    ) -> Result<(), AspaValidateError> {
        if ee.tbs.extensions.ip_resources.is_some() {
            return Err(AspaValidateError::EeIpResourcesPresent);
        }

        let asn = ee
            .tbs
            .extensions
            .as_resources
            .as_ref()
            .ok_or(AspaValidateError::EeAsResourcesMissing)?;

        if asn.rdi.is_some() {
            return Err(AspaValidateError::EeAsResourcesRdiPresent);
        }
        if asn.is_asnum_inherit() {
            return Err(AspaValidateError::EeAsResourcesInherit);
        }
        if asn.has_any_range() {
            return Err(AspaValidateError::EeAsResourcesRangePresent);
        }

        let ee_as_id = asn
            .asnum_single_id()
            .ok_or(AspaValidateError::EeAsResourcesNotSingleId)?;
        if ee_as_id != self.customer_as_id {
            return Err(AspaValidateError::CustomerAsIdMismatch {
                customer_as_id: self.customer_as_id,
                ee_as_id,
            });
        }

        Ok(())
    }
}

impl AspaObjectParsed {
    pub fn validate_profile(self) -> Result<AspaObject, AspaProfileError> {
        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_ASPA {
            return Err(AspaProfileError::InvalidEContentType(econtent_type));
        }
        let aspa = self
            .aspa
            .ok_or_else(|| AspaProfileError::ProfileDecode("ASPA.eContent missing".into()))?
            .validate_profile()?;
        Ok(AspaObject {
            signed_object,
            econtent_type: OID_CT_ASPA.to_string(),
            aspa,
        })
    }
}

impl AspaEContentParsed {
    pub fn validate_profile(self) -> Result<AspaEContent, AspaProfileError> {
        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| AspaProfileError::ProfileDecode(e))?;
        if !r.is_empty() {
            return Err(AspaProfileError::ProfileDecode(
                "trailing bytes after ASProviderAttestation".into(),
            ));
        }

        let elem_count =
            count_elements(seq).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
        if elem_count != 3 {
            return Err(AspaProfileError::InvalidAttestationSequence);
        }

        // version [0] EXPLICIT INTEGER MUST be present and MUST be 1.
        if seq
            .peek_tag()
            .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?
            != 0xA0
        {
            return Err(AspaProfileError::VersionMustBeExplicitOne);
        }
        let (inner_tag, inner_val) = seq
            .take_explicit(0xA0)
            .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
        if inner_tag != 0x02 {
            return Err(AspaProfileError::VersionMustBeExplicitOne);
        }
        let v = crate::data_model::common::der_uint_from_bytes(inner_val)
            .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
        if v != 1 {
            return Err(AspaProfileError::VersionMustBeExplicitOne);
        }

        let customer_u64 = seq
            .take_uint_u64()
            .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
        if customer_u64 > u32::MAX as u64 {
            return Err(AspaProfileError::CustomerAsIdOutOfRange(customer_u64));
        }
        let customer_as_id = customer_u64 as u32;

        let providers_seq = seq
            .take_sequence()
            .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
        if !seq.is_empty() {
            return Err(AspaProfileError::InvalidAttestationSequence);
        }
        let providers = parse_providers_cursor(providers_seq, customer_as_id)?;

        Ok(AspaEContent {
            version: 1,
            customer_as_id,
            provider_as_ids: providers,
        })
    }
}

fn parse_providers_cursor(
    mut seq: DerReader<'_>,
    customer_as_id: u32,
) -> Result<Vec<u32>, AspaProfileError> {
    if seq.is_empty() {
        return Err(AspaProfileError::EmptyProviders);
    }

    let mut out: Vec<u32> = Vec::new();
    let mut prev: Option<u32> = None;
    while !seq.is_empty() {
        let v = seq
            .take_uint_u64()
            .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
        if v > u32::MAX as u64 {
            return Err(AspaProfileError::ProviderAsIdOutOfRange(v));
        }
        let asn = v as u32;
        if asn == customer_as_id {
            return Err(AspaProfileError::ProvidersContainCustomer(customer_as_id));
        }
        if let Some(p) = prev {
            if asn <= p {
                return Err(AspaProfileError::ProvidersNotStrictlyIncreasing);
            }
        }
        prev = Some(asn);
        out.push(asn);
    }
    Ok(out)
}
