use crate::data_model::common::{BigUnsigned, der_take_tlv};
use crate::data_model::oid::{OID_CT_RPKI_CCR, OID_SHA256};

pub const CCR_VERSION_V0: u32 = 0;
pub const DIGEST_LEN_SHA256: usize = 32;
pub const KEY_IDENTIFIER_LEN_SHA1: usize = 20;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CcrDigestAlgorithm {
    Sha256,
}

impl CcrDigestAlgorithm {
    pub fn oid(&self) -> &'static str {
        match self {
            Self::Sha256 => OID_SHA256,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CcrContentInfo {
    pub content_type_oid: String,
    pub content: RpkiCanonicalCacheRepresentation,
}

impl CcrContentInfo {
    pub fn new(content: RpkiCanonicalCacheRepresentation) -> Self {
        Self {
            content_type_oid: OID_CT_RPKI_CCR.to_string(),
            content,
        }
    }

    pub fn validate(&self) -> Result<(), String> {
        if self.content_type_oid != OID_CT_RPKI_CCR {
            return Err(format!(
                "contentType must be {OID_CT_RPKI_CCR}, got {}",
                self.content_type_oid
            ));
        }
        self.content.validate()
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpkiCanonicalCacheRepresentation {
    pub version: u32,
    pub hash_alg: CcrDigestAlgorithm,
    pub produced_at: time::OffsetDateTime,
    pub mfts: Option<ManifestState>,
    pub vrps: Option<RoaPayloadState>,
    pub vaps: Option<AspaPayloadState>,
    pub tas: Option<TrustAnchorState>,
    pub rks: Option<RouterKeyState>,
}

impl RpkiCanonicalCacheRepresentation {
    pub fn validate(&self) -> Result<(), String> {
        if self.version != CCR_VERSION_V0 {
            return Err(format!("CCR version must be 0, got {}", self.version));
        }
        if !matches!(self.hash_alg, CcrDigestAlgorithm::Sha256) {
            return Err("CCR hashAlg must be SHA-256".into());
        }
        if self.mfts.is_none()
            && self.vrps.is_none()
            && self.vaps.is_none()
            && self.tas.is_none()
            && self.rks.is_none()
        {
            return Err("at least one of mfts/vrps/vaps/tas/rks must be present".into());
        }
        if let Some(mfts) = &self.mfts {
            mfts.validate()?;
        }
        if let Some(vrps) = &self.vrps {
            vrps.validate()?;
        }
        if let Some(vaps) = &self.vaps {
            vaps.validate()?;
        }
        if let Some(tas) = &self.tas {
            tas.validate()?;
        }
        if let Some(rks) = &self.rks {
            rks.validate()?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ManifestState {
    pub mis: Vec<ManifestInstance>,
    pub most_recent_update: time::OffsetDateTime,
    pub hash: Vec<u8>,
}

impl ManifestState {
    pub fn validate(&self) -> Result<(), String> {
        validate_sha256_digest("ManifestState.hash", &self.hash)?;
        validate_sorted_unique_by(
            &self.mis,
            |item| item.hash.as_slice(),
            "ManifestState.mis must be sorted by hash and unique",
        )?;
        for instance in &self.mis {
            instance.validate()?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ManifestInstance {
    pub hash: Vec<u8>,
    pub size: u64,
    pub aki: Vec<u8>,
    pub manifest_number: BigUnsigned,
    pub this_update: time::OffsetDateTime,
    pub locations: Vec<Vec<u8>>,
    pub subordinates: Vec<Vec<u8>>,
}

impl ManifestInstance {
    pub fn validate(&self) -> Result<(), String> {
        validate_sha256_digest("ManifestInstance.hash", &self.hash)?;
        if self.size < 1000 {
            return Err(format!(
                "ManifestInstance.size must be >= 1000, got {}",
                self.size
            ));
        }
        validate_key_identifier("ManifestInstance.aki", &self.aki)?;
        validate_big_unsigned_bytes(
            "ManifestInstance.manifest_number",
            &self.manifest_number.bytes_be,
        )?;
        if self.locations.is_empty() {
            return Err(
                "ManifestInstance.locations must contain at least one AccessDescription".into(),
            );
        }
        for location in &self.locations {
            validate_full_der_with_tag("ManifestInstance.locations[]", location, Some(0x30))?;
        }
        if !self.subordinates.is_empty() {
            validate_sorted_unique_bytes(
                &self.subordinates,
                KEY_IDENTIFIER_LEN_SHA1,
                "ManifestInstance.subordinates must be sorted/unique 20-byte SKIs",
            )?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaPayloadState {
    pub rps: Vec<RoaPayloadSet>,
    pub hash: Vec<u8>,
}

impl RoaPayloadState {
    pub fn validate(&self) -> Result<(), String> {
        validate_sha256_digest("ROAPayloadState.hash", &self.hash)?;
        validate_sorted_unique_by(
            &self.rps,
            |item| &item.as_id,
            "ROAPayloadState.rps must be sorted by asID and unique",
        )?;
        for set in &self.rps {
            set.validate()?;
        }
        Ok(())
    }
}

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

impl RoaPayloadSet {
    pub fn validate(&self) -> Result<(), String> {
        if self.ip_addr_blocks.is_empty() || self.ip_addr_blocks.len() > 2 {
            return Err(format!(
                "ROAPayloadSet.ip_addr_blocks must contain 1..=2 entries, got {}",
                self.ip_addr_blocks.len()
            ));
        }
        for block in &self.ip_addr_blocks {
            validate_full_der_with_tag("ROAPayloadSet.ip_addr_blocks[]", block, Some(0x30))?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AspaPayloadState {
    pub aps: Vec<AspaPayloadSet>,
    pub hash: Vec<u8>,
}

impl AspaPayloadState {
    pub fn validate(&self) -> Result<(), String> {
        validate_sha256_digest("ASPAPayloadState.hash", &self.hash)?;
        validate_sorted_unique_by(
            &self.aps,
            |item| &item.customer_as_id,
            "ASPAPayloadState.aps must be sorted by customerASID and unique",
        )?;
        for set in &self.aps {
            set.validate()?;
        }
        Ok(())
    }
}

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

impl AspaPayloadSet {
    pub fn validate(&self) -> Result<(), String> {
        if self.providers.is_empty() {
            return Err("ASPAPayloadSet.providers must be non-empty".into());
        }
        validate_sorted_unique_by(
            &self.providers,
            |provider| provider,
            "ASPAPayloadSet.providers must be sorted ascending and unique",
        )
    }
}

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

impl TrustAnchorState {
    pub fn validate(&self) -> Result<(), String> {
        if self.skis.is_empty() {
            return Err("TrustAnchorState.skis must be non-empty".into());
        }
        validate_sha256_digest("TrustAnchorState.hash", &self.hash)?;
        validate_sorted_unique_bytes(
            &self.skis,
            KEY_IDENTIFIER_LEN_SHA1,
            "TrustAnchorState.skis must be sorted/unique 20-byte SKIs",
        )
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RouterKeyState {
    pub rksets: Vec<RouterKeySet>,
    pub hash: Vec<u8>,
}

impl RouterKeyState {
    pub fn validate(&self) -> Result<(), String> {
        validate_sha256_digest("RouterKeyState.hash", &self.hash)?;
        validate_sorted_unique_by(
            &self.rksets,
            |item| &item.as_id,
            "RouterKeyState.rksets must be sorted by asID and unique",
        )?;
        for rkset in &self.rksets {
            rkset.validate()?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RouterKeySet {
    pub as_id: u32,
    pub router_keys: Vec<RouterKey>,
}

impl RouterKeySet {
    pub fn validate(&self) -> Result<(), String> {
        if self.router_keys.is_empty() {
            return Err("RouterKeySet.router_keys must be non-empty".into());
        }
        validate_sorted_unique_by(
            &self.router_keys,
            |key| key,
            "RouterKeySet.router_keys must be sorted by SKI and unique by (SKI, SPKI DER)",
        )?;
        for key in &self.router_keys {
            key.validate()?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RouterKey {
    pub ski: Vec<u8>,
    pub spki_der: Vec<u8>,
}

impl RouterKey {
    pub fn validate(&self) -> Result<(), String> {
        validate_key_identifier("RouterKey.ski", &self.ski)?;
        validate_full_der_with_tag("RouterKey.spki_der", &self.spki_der, Some(0x30))
    }
}

fn validate_sha256_digest(field: &str, bytes: &[u8]) -> Result<(), String> {
    if bytes.len() != DIGEST_LEN_SHA256 {
        return Err(format!(
            "{field} must be {DIGEST_LEN_SHA256} bytes, got {}",
            bytes.len()
        ));
    }
    Ok(())
}

fn validate_key_identifier(field: &str, bytes: &[u8]) -> Result<(), String> {
    if bytes.len() != KEY_IDENTIFIER_LEN_SHA1 {
        return Err(format!(
            "{field} must be {KEY_IDENTIFIER_LEN_SHA1} bytes, got {}",
            bytes.len()
        ));
    }
    Ok(())
}

fn validate_big_unsigned_bytes(field: &str, bytes: &[u8]) -> Result<(), String> {
    if bytes.is_empty() {
        return Err(format!("{field} must not be empty"));
    }
    if bytes.len() > 1 && bytes[0] == 0x00 {
        return Err(format!(
            "{field} must be minimally encoded as an unsigned integer"
        ));
    }
    Ok(())
}

fn validate_sorted_unique_by<T, K: Ord + ?Sized>(
    values: &[T],
    key_fn: impl Fn(&T) -> &K,
    message: &str,
) -> Result<(), String> {
    for window in values.windows(2) {
        if key_fn(&window[0]) >= key_fn(&window[1]) {
            return Err(message.to_string());
        }
    }
    Ok(())
}

fn validate_sorted_unique_bytes(
    values: &[Vec<u8>],
    expected_len: usize,
    message: &str,
) -> Result<(), String> {
    for value in values {
        if value.len() != expected_len {
            return Err(message.to_string());
        }
    }
    for window in values.windows(2) {
        if window[0] >= window[1] {
            return Err(message.to_string());
        }
    }
    Ok(())
}

fn validate_full_der_with_tag(
    field: &str,
    der: &[u8],
    expected_tag: Option<u8>,
) -> Result<(), String> {
    let (tag, _value, rem) = der_take_tlv(der).map_err(|e| format!("{field}: {e}"))?;
    if !rem.is_empty() {
        return Err(format!("{field}: trailing bytes after DER object"));
    }
    if let Some(expected_tag) = expected_tag {
        if tag != expected_tag {
            return Err(format!(
                "{field}: unexpected tag 0x{tag:02X}, expected 0x{expected_tag:02X}"
            ));
        }
    }
    Ok(())
}
