use crate::cir::model::{
    CIR_VERSION_V4, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
    CirTrustAnchor,
};
use crate::data_model::common::DerReader;
use crate::data_model::oid::{OID_SHA256, OID_SHA256_RAW};
use der_parser::der::parse_der_oid;

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

    #[error("unexpected CIR version: expected {expected}, got {actual}")]
    UnexpectedVersion { expected: u32, actual: u32 },

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

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

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

    let version = seq.take_uint_u64().map_err(CirDecodeError::Parse)? as u32;
    if version != CIR_VERSION_V4 {
        return Err(CirDecodeError::UnexpectedVersion {
            expected: CIR_VERSION_V4,
            actual: version,
        });
    }
    let hash_alg = decode_hash_alg(seq.take_tag(0x06).map_err(CirDecodeError::Parse)?)?;
    let validation_time =
        parse_generalized_time(seq.take_tag(0x18).map_err(CirDecodeError::Parse)?)?;

    let fresh_objects_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
    let mut fresh_objects_reader = DerReader::new(fresh_objects_der);
    let mut fresh_validated_objects = Vec::new();
    while !fresh_objects_reader.is_empty() {
        let (_tag, full, _value) = fresh_objects_reader
            .take_any_full()
            .map_err(CirDecodeError::Parse)?;
        fresh_validated_objects.push(decode_object(full)?);
    }

    let cached_objects_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
    let mut cached_objects_reader = DerReader::new(cached_objects_der);
    let mut cached_validated_objects = Vec::new();
    while !cached_objects_reader.is_empty() {
        let (_tag, full, _value) = cached_objects_reader
            .take_any_full()
            .map_err(CirDecodeError::Parse)?;
        cached_validated_objects.push(decode_object(full)?);
    }

    let trust_anchors_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
    let mut trust_anchors_reader = DerReader::new(trust_anchors_der);
    let mut trust_anchors = Vec::new();
    while !trust_anchors_reader.is_empty() {
        let (_tag, full, _value) = trust_anchors_reader
            .take_any_full()
            .map_err(CirDecodeError::Parse)?;
        trust_anchors.push(decode_trust_anchor(full)?);
    }

    let object_list_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let fresh_object_list_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let cached_object_list_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let reject_list_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let fresh_reject_list_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let cached_reject_list_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();

    let fresh_rejected_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
    let mut fresh_rejected_reader = DerReader::new(fresh_rejected_der);
    let mut fresh_rejected_objects = Vec::new();
    while !fresh_rejected_reader.is_empty() {
        let (_tag, full, _value) = fresh_rejected_reader
            .take_any_full()
            .map_err(CirDecodeError::Parse)?;
        fresh_rejected_objects.push(decode_rejected_object(full)?);
    }

    let cached_rejected_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
    let mut cached_rejected_reader = DerReader::new(cached_rejected_der);
    let mut cached_rejected_objects = Vec::new();
    while !cached_rejected_reader.is_empty() {
        let (_tag, full, _value) = cached_rejected_reader
            .take_any_full()
            .map_err(CirDecodeError::Parse)?;
        cached_rejected_objects.push(decode_rejected_object(full)?);
    }

    if !seq.is_empty() {
        return Err(CirDecodeError::Parse("trailing fields in CIR".into()));
    }

    let cir = CanonicalInputRepresentation {
        version,
        hash_alg,
        validation_time,
        fresh_validated_objects,
        cached_validated_objects,
        trust_anchors,
        object_list_sha256,
        fresh_object_list_sha256,
        cached_object_list_sha256,
        reject_list_sha256,
        fresh_reject_list_sha256,
        cached_reject_list_sha256,
        fresh_rejected_objects,
        cached_rejected_objects,
    };
    cir.validate().map_err(CirDecodeError::Validate)?;
    Ok(cir)
}

fn decode_hash_alg(raw_body: &[u8]) -> Result<CirHashAlgorithm, CirDecodeError> {
    if raw_body != OID_SHA256_RAW {
        return Err(CirDecodeError::UnexpectedDigestAlgorithm {
            expected: OID_SHA256,
            actual: oid_string(raw_body)?,
        });
    }
    Ok(CirHashAlgorithm::Sha256)
}

fn decode_object(der: &[u8]) -> Result<CirObject, CirDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CirDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CirDecodeError::Parse(
            "trailing bytes after CirObject".into(),
        ));
    }
    let rsync_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
        .map_err(|e| CirDecodeError::Parse(e.to_string()))?
        .to_string();
    let sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    if !seq.is_empty() {
        return Err(CirDecodeError::Parse("trailing fields in CirObject".into()));
    }
    Ok(CirObject { rsync_uri, sha256 })
}

fn decode_trust_anchor(der: &[u8]) -> Result<CirTrustAnchor, CirDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CirDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CirDecodeError::Parse(
            "trailing bytes after CirTrustAnchor".into(),
        ));
    }
    let ta_rsync_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
        .map_err(|e| CirDecodeError::Parse(e.to_string()))?
        .to_string();
    let tal_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
        .map_err(|e| CirDecodeError::Parse(e.to_string()))?
        .to_string();
    let tal_bytes = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let ta_certificate_der = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    let ta_certificate_sha256 = seq
        .take_octet_string()
        .map_err(CirDecodeError::Parse)?
        .to_vec();
    if !seq.is_empty() {
        return Err(CirDecodeError::Parse(
            "trailing fields in CirTrustAnchor".into(),
        ));
    }
    Ok(CirTrustAnchor {
        ta_rsync_uri,
        tal_uri,
        tal_bytes,
        ta_certificate_der,
        ta_certificate_sha256,
    })
}

fn decode_rejected_object(der: &[u8]) -> Result<CirRejectedObject, CirDecodeError> {
    let mut top = DerReader::new(der);
    let mut seq = top.take_sequence().map_err(CirDecodeError::Parse)?;
    if !top.is_empty() {
        return Err(CirDecodeError::Parse(
            "trailing bytes after CirRejectedObject".into(),
        ));
    }
    let object_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
        .map_err(|e| CirDecodeError::Parse(e.to_string()))?
        .to_string();
    let reason = if seq.is_empty() {
        None
    } else {
        Some(
            std::str::from_utf8(seq.take_octet_string().map_err(CirDecodeError::Parse)?)
                .map_err(|e| CirDecodeError::Parse(e.to_string()))?
                .to_string(),
        )
    };
    if !seq.is_empty() {
        return Err(CirDecodeError::Parse(
            "trailing fields in CirRejectedObject".into(),
        ));
    }
    Ok(CirRejectedObject { object_uri, reason })
}

fn oid_string(raw_body: &[u8]) -> Result<String, CirDecodeError> {
    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(CirDecodeError::Parse("OID too long".into()));
        }
        out.extend_from_slice(raw_body);
        out
    };
    let (_rem, oid) = parse_der_oid(&der).map_err(|e| CirDecodeError::Parse(e.to_string()))?;
    let oid = oid
        .as_oid_val()
        .map_err(|e| CirDecodeError::Parse(e.to_string()))?;
    Ok(oid.to_string())
}

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