use crate::ccr::model::{
    AspaPayloadSet, AspaPayloadState, CCR_VERSION_V0, CcrContentInfo, CcrDigestAlgorithm,
    ManifestInstance, ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet,
    RouterKeyState, RpkiCanonicalCacheRepresentation, TrustAnchorState,
};
use crate::data_model::common::{BigUnsigned, DerReader};
use crate::data_model::oid::{OID_CT_RPKI_CCR, OID_CT_RPKI_CCR_RAW, OID_SHA256, OID_SHA256_RAW};
use der_parser::der::parse_der_oid;

#[derive(Debug, thiserror::Error)]
pub enum CcrDecodeError {
    #[error("DER parse error: {0}")]
    Parse(String),

    #[error("unexpected contentType OID: expected {expected}, got {actual}")]
    UnexpectedContentType {
        expected: &'static str,
        actual: String,
    },

    #[error("unexpected digest algorithm OID: expected {expected}, got {actual}")]
    UnexpectedDigestAlgorithm {
        expected: &'static str,
        actual: String,
    },

    #[error("CCR model validation failed after decode: {0}")]
    Validate(String),
}

pub fn decode_content_info(der: &[u8]) -> Result<CcrContentInfo, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ContentInfo".into(),
        ));
    }
    let content_type_raw = seq.take_tag(0x06).map_err(CcrDecodeError::Parse)?;
    if content_type_raw != OID_CT_RPKI_CCR_RAW {
        return Err(CcrDecodeError::UnexpectedContentType {
            expected: OID_CT_RPKI_CCR,
            actual: oid_string(content_type_raw)?,
        });
    }
    let inner = seq.take_tag(0xA0).map_err(CcrDecodeError::Parse)?;
    if !seq.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing fields in ContentInfo".into(),
        ));
    }
    let content = decode_ccr(inner)?;
    let ci = CcrContentInfo::new(content);
    ci.validate().map_err(CcrDecodeError::Validate)?;
    Ok(ci)
}

pub fn decode_ccr(der: &[u8]) -> Result<RpkiCanonicalCacheRepresentation, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse("trailing bytes after CCR".into()));
    }

    let version = if !seq.is_empty() && seq.peek_tag().map_err(CcrDecodeError::Parse)? == 0xA0 {
        let explicit = seq.take_tag(0xA0).map_err(CcrDecodeError::Parse)?;
        let mut inner = DerReader::new(explicit);
        let version = inner.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32;
        if !inner.is_empty() {
            return Err(CcrDecodeError::Parse(
                "trailing bytes inside CCR version EXPLICIT".into(),
            ));
        }
        version
    } else {
        CCR_VERSION_V0
    };

    let hash_alg = decode_digest_algorithm(seq.take_sequence().map_err(CcrDecodeError::Parse)?)?;
    let produced_at = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?;

    let mut mfts = None;
    let mut vrps = None;
    let mut vaps = None;
    let mut tas = None;
    let mut rks = None;
    while !seq.is_empty() {
        let tag = seq.peek_tag().map_err(CcrDecodeError::Parse)?;
        let (tag_read, value) = seq.take_any().map_err(CcrDecodeError::Parse)?;
        debug_assert_eq!(tag, tag_read);
        match tag {
            0xA1 => mfts = Some(decode_manifest_state(value)?),
            0xA2 => vrps = Some(decode_roa_payload_state(value)?),
            0xA3 => vaps = Some(decode_aspa_payload_state(value)?),
            0xA4 => tas = Some(decode_trust_anchor_state(value)?),
            0xA5 => rks = Some(decode_router_key_state(value)?),
            _ => {
                return Err(CcrDecodeError::Parse(format!(
                    "unexpected CCR field tag 0x{tag:02X}"
                )));
            }
        }
    }

    let ccr = RpkiCanonicalCacheRepresentation {
        version,
        hash_alg,
        produced_at,
        mfts,
        vrps,
        vaps,
        tas,
        rks,
    };
    ccr.validate().map_err(CcrDecodeError::Validate)?;
    Ok(ccr)
}

fn decode_manifest_state(explicit_der: &[u8]) -> Result<ManifestState, CcrDecodeError> {
    let mut outer = DerReader::new(explicit_der);
    let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !outer.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ManifestState".into(),
        ));
    }
    let mis_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut mis_reader = DerReader::new(mis_der);
    let mut mis = Vec::new();
    while !mis_reader.is_empty() {
        let (_tag, full, _value) = mis_reader.take_any_full().map_err(CcrDecodeError::Parse)?;
        mis.push(decode_manifest_instance(full)?);
    }
    let most_recent_update =
        parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?;
    let hash = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    if !seq.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing fields in ManifestState".into(),
        ));
    }
    Ok(ManifestState {
        mis,
        most_recent_update,
        hash,
    })
}

fn decode_manifest_instance(der: &[u8]) -> Result<ManifestInstance, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ManifestInstance".into(),
        ));
    }
    let hash = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    let size = seq.take_uint_u64().map_err(CcrDecodeError::Parse)?;
    let aki = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    let manifest_number = decode_big_unsigned(seq.take_tag(0x02).map_err(CcrDecodeError::Parse)?)?;
    let this_update = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?;
    let locations_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut locations_reader = DerReader::new(locations_der);
    let mut locations = Vec::new();
    while !locations_reader.is_empty() {
        let (_tag, full, _value) = locations_reader
            .take_any_full()
            .map_err(CcrDecodeError::Parse)?;
        locations.push(full.to_vec());
    }
    let subordinates = if !seq.is_empty() {
        let subordinate_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
        let mut reader = DerReader::new(subordinate_der);
        let mut out = Vec::new();
        while !reader.is_empty() {
            out.push(
                reader
                    .take_octet_string()
                    .map_err(CcrDecodeError::Parse)?
                    .to_vec(),
            );
        }
        out
    } else {
        Vec::new()
    };
    Ok(ManifestInstance {
        hash,
        size,
        aki,
        manifest_number,
        this_update,
        locations,
        subordinates,
    })
}

fn decode_roa_payload_state(explicit_der: &[u8]) -> Result<RoaPayloadState, CcrDecodeError> {
    let mut outer = DerReader::new(explicit_der);
    let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !outer.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ROAPayloadState".into(),
        ));
    }
    let payload_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(payload_der);
    let mut rps = Vec::new();
    while !reader.is_empty() {
        let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?;
        rps.push(decode_roa_payload_set(full)?);
    }
    let hash = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    Ok(RoaPayloadState { rps, hash })
}

fn decode_roa_payload_set(der: &[u8]) -> Result<RoaPayloadSet, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ROAPayloadSet".into(),
        ));
    }
    let as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32;
    let blocks_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(blocks_der);
    let mut ip_addr_blocks = Vec::new();
    while !reader.is_empty() {
        let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?;
        ip_addr_blocks.push(full.to_vec());
    }
    Ok(RoaPayloadSet {
        as_id,
        ip_addr_blocks,
    })
}

fn decode_aspa_payload_state(explicit_der: &[u8]) -> Result<AspaPayloadState, CcrDecodeError> {
    let mut outer = DerReader::new(explicit_der);
    let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !outer.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ASPAPayloadState".into(),
        ));
    }
    let payload_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(payload_der);
    let mut aps = Vec::new();
    while !reader.is_empty() {
        let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?;
        aps.push(decode_aspa_payload_set(full)?);
    }
    let hash = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    Ok(AspaPayloadState { aps, hash })
}

fn decode_aspa_payload_set(der: &[u8]) -> Result<AspaPayloadSet, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after ASPAPayloadSet".into(),
        ));
    }
    let customer_as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32;
    let providers_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(providers_der);
    let mut providers = Vec::new();
    while !reader.is_empty() {
        providers.push(reader.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32);
    }
    Ok(AspaPayloadSet {
        customer_as_id,
        providers,
    })
}

fn decode_trust_anchor_state(explicit_der: &[u8]) -> Result<TrustAnchorState, CcrDecodeError> {
    let mut outer = DerReader::new(explicit_der);
    let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !outer.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after TrustAnchorState".into(),
        ));
    }
    let skis_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(skis_der);
    let mut skis = Vec::new();
    while !reader.is_empty() {
        skis.push(
            reader
                .take_octet_string()
                .map_err(CcrDecodeError::Parse)?
                .to_vec(),
        );
    }
    let hash = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    Ok(TrustAnchorState { skis, hash })
}

fn decode_router_key_state(explicit_der: &[u8]) -> Result<RouterKeyState, CcrDecodeError> {
    let mut outer = DerReader::new(explicit_der);
    let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !outer.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after RouterKeyState".into(),
        ));
    }
    let sets_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(sets_der);
    let mut rksets = Vec::new();
    while !reader.is_empty() {
        let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?;
        rksets.push(decode_router_key_set(full)?);
    }
    let hash = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    Ok(RouterKeyState { rksets, hash })
}

fn decode_router_key_set(der: &[u8]) -> Result<RouterKeySet, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after RouterKeySet".into(),
        ));
    }
    let as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32;
    let keys_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?;
    let mut reader = DerReader::new(keys_der);
    let mut router_keys = Vec::new();
    while !reader.is_empty() {
        let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?;
        router_keys.push(decode_router_key(full)?);
    }
    Ok(RouterKeySet { as_id, router_keys })
}

fn decode_router_key(der: &[u8]) -> Result<RouterKey, CcrDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing bytes after RouterKey".into(),
        ));
    }
    let ski = seq
        .take_octet_string()
        .map_err(CcrDecodeError::Parse)?
        .to_vec();
    let (_tag, full, _value) = seq.take_any_full().map_err(CcrDecodeError::Parse)?;
    if !seq.is_empty() {
        return Err(CcrDecodeError::Parse("trailing fields in RouterKey".into()));
    }
    Ok(RouterKey {
        ski,
        spki_der: full.to_vec(),
    })
}

fn decode_digest_algorithm(mut seq: DerReader<'_>) -> Result<CcrDigestAlgorithm, CcrDecodeError> {
    let oid_raw = seq.take_tag(0x06).map_err(CcrDecodeError::Parse)?;
    if oid_raw != OID_SHA256_RAW {
        return Err(CcrDecodeError::UnexpectedDigestAlgorithm {
            expected: OID_SHA256,
            actual: oid_string(oid_raw)?,
        });
    }
    if !seq.is_empty() {
        let tag = seq.peek_tag().map_err(CcrDecodeError::Parse)?;
        if tag == 0x05 {
            let null = seq.take_tag(0x05).map_err(CcrDecodeError::Parse)?;
            if !null.is_empty() {
                return Err(CcrDecodeError::Parse(
                    "AlgorithmIdentifier NULL parameters must be empty".into(),
                ));
            }
        }
    }
    if !seq.is_empty() {
        return Err(CcrDecodeError::Parse(
            "trailing fields in DigestAlgorithmIdentifier".into(),
        ));
    }
    Ok(CcrDigestAlgorithm::Sha256)
}

fn oid_string(raw_body: &[u8]) -> Result<String, CcrDecodeError> {
    let der = {
        let mut out = Vec::with_capacity(raw_body.len() + 2);
        out.push(0x06);
        if raw_body.len() < 0x80 {
            out.push(raw_body.len() as u8);
        } else {
            return Err(CcrDecodeError::Parse("OID too long".into()));
        }
        out.extend_from_slice(raw_body);
        out
    };
    let (_rem, oid) = parse_der_oid(&der).map_err(|e| CcrDecodeError::Parse(e.to_string()))?;
    let oid = oid
        .as_oid_val()
        .map_err(|e| CcrDecodeError::Parse(e.to_string()))?;
    Ok(oid.to_string())
}

fn parse_generalized_time(bytes: &[u8]) -> Result<time::OffsetDateTime, CcrDecodeError> {
    let s = std::str::from_utf8(bytes).map_err(|e| CcrDecodeError::Parse(e.to_string()))?;
    if s.len() != 15 || !s.ends_with('Z') {
        return Err(CcrDecodeError::Parse(
            "GeneralizedTime must be YYYYMMDDHHMMSSZ".into(),
        ));
    }
    let parse = |range: std::ops::Range<usize>| -> Result<u32, CcrDecodeError> {
        s[range]
            .parse::<u32>()
            .map_err(|e| CcrDecodeError::Parse(e.to_string()))
    };
    let year = parse(0..4)? as i32;
    let month = parse(4..6)? as u8;
    let day = parse(6..8)? as u8;
    let hour = parse(8..10)? as u8;
    let minute = parse(10..12)? as u8;
    let second = parse(12..14)? as u8;
    let month = time::Month::try_from(month).map_err(|e| CcrDecodeError::Parse(e.to_string()))?;
    let date = time::Date::from_calendar_date(year, month, day)
        .map_err(|e| CcrDecodeError::Parse(e.to_string()))?;
    let timev = time::Time::from_hms(hour, minute, second)
        .map_err(|e| CcrDecodeError::Parse(e.to_string()))?;
    Ok(time::PrimitiveDateTime::new(date, timev).assume_utc())
}

fn decode_big_unsigned(bytes: &[u8]) -> Result<BigUnsigned, CcrDecodeError> {
    if bytes.is_empty() {
        return Err(CcrDecodeError::Parse("INTEGER has empty content".into()));
    }
    if bytes[0] & 0x80 != 0 {
        return Err(CcrDecodeError::Parse("INTEGER must be non-negative".into()));
    }
    if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 {
        return Err(CcrDecodeError::Parse(
            "INTEGER not minimally encoded".into(),
        ));
    }
    let bytes_be = if bytes.len() > 1 && bytes[0] == 0x00 {
        bytes[1..].to_vec()
    } else {
        bytes.to_vec()
    };
    Ok(BigUnsigned { bytes_be })
}

#[cfg(all(test, feature = "full"))]
mod tests {
    use super::*;
    use crate::ccr::encode::{encode_content_info, encode_manifest_state_payload_der};
    use crate::ccr::hash::compute_state_hash;
    use crate::ccr::manifest_location::encode_access_description_der;
    use crate::ccr::model::{
        CcrContentInfo, CcrDigestAlgorithm, ManifestState, RpkiCanonicalCacheRepresentation,
    };
    use crate::data_model::oid::{OID_AD_RPKI_NOTIFY, OID_AD_SIGNED_OBJECT};
    use crate::data_model::rc::AccessDescription;

    #[test]
    fn generic_decoder_preserves_multiple_manifest_locations() {
        let signed_object = encode_access_description_der(&AccessDescription {
            access_method_oid: OID_AD_SIGNED_OBJECT.to_string(),
            access_location: "rsync://example.test/repository/current.mft".to_string(),
        })
        .expect("encode signedObject");
        let rpki_notify = encode_access_description_der(&AccessDescription {
            access_method_oid: OID_AD_RPKI_NOTIFY.to_string(),
            access_location: "https://rrdp.example.test/notification.xml".to_string(),
        })
        .expect("encode rpkiNotify");
        let manifest = ManifestInstance {
            hash: vec![0x11; 32],
            size: 1024,
            aki: vec![0x22; 20],
            manifest_number: BigUnsigned { bytes_be: vec![1] },
            this_update: time::OffsetDateTime::parse(
                "2026-07-20T00:00:00Z",
                &time::format_description::well_known::Rfc3339,
            )
            .expect("parse time"),
            locations: vec![signed_object, rpki_notify],
            subordinates: Vec::new(),
        };
        let manifest_payload = encode_manifest_state_payload_der(&[manifest.clone()])
            .expect("encode manifest payload");
        let content = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
            version: 0,
            hash_alg: CcrDigestAlgorithm::Sha256,
            produced_at: time::OffsetDateTime::parse(
                "2026-07-20T00:00:00Z",
                &time::format_description::well_known::Rfc3339,
            )
            .expect("parse time"),
            mfts: Some(ManifestState {
                mis: vec![manifest],
                most_recent_update: time::OffsetDateTime::parse(
                    "2026-07-20T00:00:00Z",
                    &time::format_description::well_known::Rfc3339,
                )
                .expect("parse time"),
                hash: compute_state_hash(&manifest_payload),
            }),
            vrps: None,
            vaps: None,
            tas: None,
            rks: None,
        });

        let encoded = encode_content_info(&content).expect("encode CCR");
        let decoded = decode_content_info(&encoded).expect("decode CCR");
        assert_eq!(decoded.content.mfts.unwrap().mis[0].locations.len(), 2);
    }
}
