use x509_parser::asn1_rs::Tag;
use x509_parser::prelude::FromDer;
use x509_parser::x509::AlgorithmIdentifier;

pub type UtcTime = time::OffsetDateTime;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Asn1TimeEncoding {
    UtcTime,
    GeneralizedTime,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Asn1TimeUtc {
    pub utc: UtcTime,
    pub encoding: Asn1TimeEncoding,
}

impl Asn1TimeUtc {
    /// Validate Time encoding rules (RFC 5280): years 1950-2049 use UTCTime,
    /// other years use GeneralizedTime.
    pub fn validate_encoding_rfc5280(
        &self,
        field: &'static str,
    ) -> Result<(), InvalidTimeEncodingError> {
        let year = self.utc.year();
        let expected = if year <= 2049 {
            Asn1TimeEncoding::UtcTime
        } else {
            Asn1TimeEncoding::GeneralizedTime
        };
        if self.encoding != expected {
            return Err(InvalidTimeEncodingError {
                field,
                year,
                encoding: self.encoding,
            });
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigUnsigned {
    /// Minimal big-endian bytes. For zero, this is `[0]`.
    pub bytes_be: Vec<u8>,
}

impl BigUnsigned {
    pub fn from_biguint(n: &der_parser::num_bigint::BigUint) -> Self {
        let mut bytes = n.to_bytes_be();
        if bytes.is_empty() {
            bytes.push(0);
        }
        Self { bytes_be: bytes }
    }

    pub fn to_hex_upper(&self) -> String {
        hex::encode_upper(&self.bytes_be)
    }

    pub fn to_u64(&self) -> Option<u64> {
        if self.bytes_be.len() > 8 {
            return None;
        }
        let mut value: u64 = 0;
        for &b in &self.bytes_be {
            value = (value << 8) | (b as u64);
        }
        Some(value)
    }
}

#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error(
    "{field} time encoding invalid for year {year}: got {encoding:?} (RFC 5280 §4.1.2.5; RFC 5280 §5.1.2.4-§5.1.2.6)"
)]
pub struct InvalidTimeEncodingError {
    pub field: &'static str,
    pub year: i32,
    pub encoding: Asn1TimeEncoding,
}

pub fn asn1_time_to_model(t: x509_parser::time::ASN1Time) -> Asn1TimeUtc {
    let encoding = if t.is_utctime() {
        Asn1TimeEncoding::UtcTime
    } else {
        Asn1TimeEncoding::GeneralizedTime
    };
    Asn1TimeUtc {
        utc: t.to_datetime(),
        encoding,
    }
}

pub fn algorithm_params_absent_or_null(sig: &AlgorithmIdentifier<'_>) -> bool {
    match sig.parameters.as_ref() {
        None => true,
        Some(p) if p.tag() == Tag::Null => true,
        Some(_p) => false,
    }
}

/// Take a single DER TLV (Tag-Length-Value) from the start of `input`.
///
/// This helper supports:
/// - short- and long-form lengths (up to 8 length bytes)
/// - only low-tag-number form tags (no high-tag-number form)
/// - definite length only (DER forbids indefinite length)
///
/// Returns: `(tag_byte, value_bytes, remaining_bytes)`.
pub(crate) fn der_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
    if input.len() < 2 {
        return Err("truncated DER (need tag+len)".into());
    }
    let tag = input[0];
    if (tag & 0x1F) == 0x1F {
        return Err("high-tag-number form not supported".into());
    }
    let len0 = input[1];
    if len0 == 0x80 {
        return Err("indefinite length not allowed in DER".into());
    }
    let (len, hdr_len) = if len0 & 0x80 == 0 {
        (len0 as usize, 2usize)
    } else {
        let n = (len0 & 0x7F) as usize;
        if n == 0 || n > 8 {
            return Err("invalid DER length".into());
        }
        if input.len() < 2 + n {
            return Err("truncated DER (length bytes)".into());
        }
        let mut l: usize = 0;
        for &b in &input[2..2 + n] {
            l = (l << 8) | (b as usize);
        }
        (l, 2 + n)
    };
    if input.len() < hdr_len + len {
        return Err("truncated DER (value bytes)".into());
    }
    let value = &input[hdr_len..hdr_len + len];
    let rem = &input[hdr_len + len..];
    Ok((tag, value, rem))
}

/// Minimal streaming DER reader built on `der_take_tlv`.
///
/// This is intentionally small and only supports the subset of DER needed by
/// RPKI object eContent decoders (ROA/ASPA), to avoid constructing a generic AST
/// (which is expensive on large objects such as ROAs with thousands of prefixes).
#[derive(Clone, Copy)]
pub(crate) struct DerReader<'a> {
    buf: &'a [u8],
}

impl<'a> DerReader<'a> {
    pub(crate) fn new(buf: &'a [u8]) -> Self {
        Self { buf }
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.buf.is_empty()
    }

    pub(crate) fn remaining_len(&self) -> usize {
        self.buf.len()
    }

    pub(crate) fn peek_tag(&self) -> Result<u8, String> {
        self.buf
            .first()
            .copied()
            .ok_or_else(|| "truncated DER".into())
    }

    pub(crate) fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
        let (tag, value, rem) = der_take_tlv(self.buf)?;
        self.buf = rem;
        Ok((tag, value))
    }

    pub(crate) fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
        let (tag, value, rem) = der_take_tlv(self.buf)?;
        let consumed = self.buf.len() - rem.len();
        let full = &self.buf[..consumed];
        self.buf = rem;
        Ok((tag, full, value))
    }

    pub(crate) fn skip_any(&mut self) -> Result<(), String> {
        let _ = self.take_any()?;
        Ok(())
    }

    pub(crate) fn take_tag(&mut self, expected_tag: u8) -> Result<&'a [u8], String> {
        let (tag, value) = self.take_any()?;
        if tag != expected_tag {
            return Err(format!(
                "unexpected tag: got 0x{tag:02X}, expected 0x{expected_tag:02X}"
            ));
        }
        Ok(value)
    }

    pub(crate) fn take_sequence(&mut self) -> Result<DerReader<'a>, String> {
        let value = self.take_tag(0x30)?;
        Ok(DerReader::new(value))
    }

    pub(crate) fn take_octet_string(&mut self) -> Result<&'a [u8], String> {
        self.take_tag(0x04)
    }

    pub(crate) fn take_bit_string(&mut self) -> Result<(u8, &'a [u8]), String> {
        let v = self.take_tag(0x03)?;
        if v.is_empty() {
            return Err("BIT STRING content is empty".into());
        }
        Ok((v[0], &v[1..]))
    }

    pub(crate) fn take_uint_u64(&mut self) -> Result<u64, String> {
        let v = self.take_tag(0x02)?;
        der_uint_from_bytes(v)
    }

    pub(crate) fn take_explicit(
        &mut self,
        expected_outer_tag: u8,
    ) -> Result<(u8, &'a [u8]), String> {
        let inner_der = self.take_tag(expected_outer_tag)?;
        let (tag, value, rem) = der_take_tlv(inner_der)?;
        if !rem.is_empty() {
            return Err("trailing bytes inside EXPLICIT value".into());
        }
        Ok((tag, value))
    }

    pub(crate) fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> {
        let inner_der = self.take_tag(expected_outer_tag)?;
        let (_tag, _value, rem) = der_take_tlv(inner_der)?;
        if !rem.is_empty() {
            return Err("trailing bytes inside EXPLICIT value".into());
        }
        Ok(inner_der)
    }
}

pub(crate) fn der_uint_from_bytes(bytes: &[u8]) -> Result<u64, String> {
    if bytes.is_empty() {
        return Err("INTEGER has empty content".into());
    }
    // Disallow negative values.
    if (bytes[0] & 0x80) != 0 {
        return Err("INTEGER is negative".into());
    }
    // DER requires minimal encoding for INTEGER.
    if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 {
        return Err("INTEGER not minimally encoded".into());
    }
    if bytes.len() > 8 {
        return Err("INTEGER does not fit u64".into());
    }
    let mut v: u64 = 0;
    for &b in bytes {
        v = (v << 8) | (b as u64);
    }
    Ok(v)
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct X509NameDer(pub Vec<u8>);

impl X509NameDer {
    pub fn as_raw(&self) -> &[u8] {
        &self.0
    }
}

impl std::fmt::Display for X509NameDer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Ok((rem, name)) = x509_parser::x509::X509Name::from_der(&self.0) else {
            return write!(f, "<invalid X.509 Name DER>");
        };
        if !rem.is_empty() {
            return write!(f, "<invalid X.509 Name DER (trailing bytes)>");
        }
        write!(f, "{name}")
    }
}

/// Filename extensions registered in IANA "RPKI Repository Name Schemes".
///
/// Source: <https://www.iana.org/assignments/rpki/rpki.xhtml>
/// Snapshot date: 2026-01-28.
///
/// Notes:
/// - Includes entries marked TEMPORARY/DEPRECATED by IANA (e.g., `asa`, `gbr`).
pub const IANA_RPKI_REPOSITORY_FILENAME_EXTENSIONS: &[&str] =
    &["asa", "cer", "crl", "gbr", "mft", "roa", "sig", "tak"];
