// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Common types shared across CoRIM and CoMID structures.
//!
//! All custom serde impls use [`crate::cbor::value::Value`] for tag dispatch
//! rather than importing any specific CBOR backend directly.

#[allow(unused_imports)]
use crate::nostd_prelude::*;
use corim_macros::{
    CborDeserialize, CborSerialize, CborTagChoiceDeserialize, CborTagChoiceSerialize,
};
use serde::{Deserialize, Serialize};

use super::tags::*;
use crate::cbor::value::{self, Value};
use crate::types::measurement::Digest;

// ---------------------------------------------------------------------------
// CborTime — CBOR epoch-based date/time (#6.1)
// ---------------------------------------------------------------------------

/// CBOR epoch-based date/time per RFC 8949 §3.4.2.
///
/// Serializes as `#6.1(int)`. Deserializes accepting both tagged (`#6.1(int)`)
/// and untagged `int` for interoperability.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CborTime(pub i64);

impl CborTime {
    /// Create a new epoch time value.
    pub fn new(epoch_secs: i64) -> Self {
        Self(epoch_secs)
    }

    /// Get the epoch seconds value.
    pub fn epoch_secs(self) -> i64 {
        self.0
    }
}

impl From<i64> for CborTime {
    fn from(v: i64) -> Self {
        Self(v)
    }
}

impl From<CborTime> for i64 {
    fn from(t: CborTime) -> Self {
        t.0
    }
}

impl core::fmt::Display for CborTime {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        // Format as "YYYY-MM-DD HH:MM:SS UTC" when possible, fallback to epoch.
        // Algorithm: convert epoch seconds to date components without std.
        let secs = self.0;
        if secs < 0 {
            return write!(f, "{}(epoch)", secs);
        }
        let s = secs as u64;
        let days = s / 86400;
        let time_of_day = s % 86400;
        let h = time_of_day / 3600;
        let m = (time_of_day % 3600) / 60;
        let sec = time_of_day % 60;

        // Civil date from day count (algorithm from Howard Hinnant)
        let z = days + 719468;
        let era = z / 146097;
        let doe = z - era * 146097;
        let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
        let y = yoe + era * 400;
        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
        let mp = (5 * doy + 2) / 153;
        let d = doy - (153 * mp + 2) / 5 + 1;
        let mon = if mp < 10 { mp + 3 } else { mp - 9 };
        let year = if mon <= 2 { y + 1 } else { y };

        write!(
            f,
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
            year, mon, d, h, m, sec
        )
    }
}

impl Serialize for CborTime {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        // Serialize as #6.1(int) per RFC 8949 §3.4.2
        value::serialize_tagged(TAG_EPOCH_TIME, &self.0, s)
    }
}

impl<'de> Deserialize<'de> for CborTime {
    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let val = Value::deserialize(d)?;
        match val {
            // Accept tagged #6.1(int)
            Value::Tag(TAG_EPOCH_TIME, inner) => {
                let n = inner
                    .into_integer()
                    .ok_or_else(|| serde::de::Error::custom("tag 1 must wrap integer"))?;
                let n: i64 = n
                    .try_into()
                    .map_err(|_| serde::de::Error::custom("epoch time out of i64 range"))?;
                Ok(CborTime(n))
            }
            // Also accept untagged int for interop with non-conformant producers
            Value::Integer(n) => {
                let n: i64 = n
                    .try_into()
                    .map_err(|_| serde::de::Error::custom("epoch time out of i64 range"))?;
                Ok(CborTime(n))
            }
            _ => Err(serde::de::Error::custom("expected time (tag 1 or integer)")),
        }
    }
}

// ---------------------------------------------------------------------------
// tag-identity-map  { tag-id: 0, tag-version: 1 }
// ---------------------------------------------------------------------------

/// `tag-identity-map` — identifies a CoMID tag.
#[derive(Clone, Debug, PartialEq, CborSerialize, CborDeserialize)]
pub struct TagIdentity {
    /// `tag-id` (key 0): globally unique tag identifier.
    #[cbor(key = 0)]
    pub tag_id: TagIdChoice,
    /// `tag-version` (key 1): optional revision number.
    ///
    /// Per CDDL `uint .default 0`, absent means version 0.
    /// Use [`tag_version_or_default`](TagIdentity::tag_version_or_default)
    /// to get the effective value.
    #[cbor(key = 1, optional)]
    pub tag_version: Option<u64>,
}

impl TagIdentity {
    /// Returns the tag version, treating `None` as the CDDL default of 0.
    pub fn tag_version_or_default(&self) -> u64 {
        self.tag_version.unwrap_or(0)
    }
}

// ---------------------------------------------------------------------------
// validity-map  { not-before: 0, not-after: 1 }
// ---------------------------------------------------------------------------

/// `validity-map` — time window.
///
/// Time values are CBOR epoch-based date/time (`#6.1(int)`) per RFC 8949 §3.4.2.
/// [`CborTime`] handles both tagged and untagged integers on decode.
#[derive(Clone, Debug, PartialEq, CborSerialize, CborDeserialize)]
pub struct ValidityMap {
    /// `not-before` (key 0): optional start of validity.
    #[cbor(key = 0, optional)]
    pub not_before: Option<CborTime>,
    /// `not-after` (key 1): end of validity.
    #[cbor(key = 1)]
    pub not_after: CborTime,
}

// ---------------------------------------------------------------------------
// entity-map  { entity-name: 0, reg-id: 1, role: 2 }
// ---------------------------------------------------------------------------

/// `entity-map` — describes an entity (creator, signer, etc.).
#[derive(Clone, Debug, PartialEq, CborSerialize, CborDeserialize)]
pub struct EntityMap {
    /// `entity-name` (key 0): name of the entity.
    #[cbor(key = 0)]
    pub entity_name: String,
    /// `reg-id` (key 1): optional URI for the organization.
    #[cbor(key = 1, optional)]
    pub reg_id: Option<String>,
    /// `role` (key 2): list of roles.
    #[cbor(key = 2)]
    pub role: Vec<i64>,
}

// ---------------------------------------------------------------------------
// version-map  { version: 0, version-scheme: 1 }
// ---------------------------------------------------------------------------

/// `version-map` — software version info.
#[derive(Clone, Debug, PartialEq, CborSerialize, CborDeserialize)]
pub struct VersionMap {
    /// `version` (key 0): the version string.
    #[cbor(key = 0)]
    pub version: String,
    /// `version-scheme` (key 1): optional versioning convention.
    #[cbor(key = 1, optional)]
    pub version_scheme: Option<i64>,
}

// ---------------------------------------------------------------------------
// Type-choice enums
// ---------------------------------------------------------------------------

/// `$tag-id-type-choice` — text string or UUID.
///
/// Decoder accepts a wire-format relaxation: a bare 16-byte `bstr` (no tag)
/// is routed to [`Uuid`](Self::Uuid). Encoders always emit the tagged form.
#[derive(Clone, Debug, PartialEq, Eq, CborTagChoiceSerialize, CborTagChoiceDeserialize)]
#[non_exhaustive]
pub enum TagIdChoice {
    /// A textual tag identifier.
    #[cbor(text)]
    Text(String),
    /// A 16-byte UUID (CBOR tag 37). Bare 16-byte `bstr` is also accepted on decode.
    #[cbor(tag = 37, bytes, accept_bare = "uuid_16")]
    Uuid([u8; 16]),
}

/// `$class-id-type-choice` — OID, UUID, or generic bytes.
///
/// Decoder accepts a few wire-format relaxations beyond the strict CDDL:
///
/// - bare 16-byte `bstr` (no tag) is routed to [`Uuid`](Self::Uuid)
/// - any other bare `bstr` falls through to [`Bytes`](Self::Bytes)
///
/// Encoders always emit the tagged form (`#6.111`, `#6.37`, or `#6.560`).
#[derive(Clone, Debug, PartialEq, Eq, CborTagChoiceSerialize, CborTagChoiceDeserialize)]
#[non_exhaustive]
pub enum ClassIdChoice {
    /// OID (CBOR tag 111).
    #[cbor(tag = 111, bytes)]
    Oid(Vec<u8>),
    /// UUID (CBOR tag 37). Bare 16-byte `bstr` (no tag) is also accepted on decode.
    #[cbor(tag = 37, bytes, accept_bare = "uuid_16")]
    Uuid([u8; 16]),
    /// Generic tagged bytes (CBOR tag 560). Catch-all for any bare `bstr`
    /// not already routed to [`Uuid`](Self::Uuid).
    #[cbor(tag = 560, bytes, catch_bare_bytes)]
    Bytes(Vec<u8>),
}

/// `$instance-id-type-choice` — UEID, UUID, bytes, or crypto key types.
///
/// Decoder accepts the same wire-format relaxations as [`ClassIdChoice`]:
///
/// - bare 16-byte `bstr` (no tag) is routed to [`Uuid`](Self::Uuid)
/// - any other bare `bstr` falls through to [`Bytes`](Self::Bytes)
///
/// The UEID variant enforces the RFC 8392 §4 size constraint (7–33
/// bytes inclusive) via a post-decode `custom_validate` hook.
#[derive(Clone, Debug, PartialEq, Eq, CborTagChoiceSerialize, CborTagChoiceDeserialize)]
#[cbor(custom_validate = "validate_instance_id_ueid")]
#[non_exhaustive]
pub enum InstanceIdChoice {
    /// UEID (CBOR tag 550). Length is constrained to 7–33 bytes.
    #[cbor(tag = 550, bytes)]
    Ueid(Vec<u8>),
    /// UUID (CBOR tag 37). Bare 16-byte `bstr` is also accepted on decode.
    #[cbor(tag = 37, bytes, accept_bare = "uuid_16")]
    Uuid([u8; 16]),
    /// Generic tagged bytes (CBOR tag 560). Catch-all for any bare `bstr`
    /// not already routed to [`Uuid`](Self::Uuid).
    #[cbor(tag = 560, bytes, catch_bare_bytes)]
    Bytes(Vec<u8>),
    /// PEM SubjectPublicKeyInfo (CBOR tag 554).
    #[cbor(tag = 554, text)]
    PkixBase64Key(String),
    /// PEM X.509 certificate (CBOR tag 555).
    #[cbor(tag = 555, text)]
    PkixBase64Cert(String),
    /// CBOR-encoded COSE_Key (CBOR tag 558).
    #[cbor(tag = 558, bytes)]
    CoseKey(Vec<u8>),
    /// Key thumbprint digest (CBOR tag 557).
    #[cbor(tag = 557)]
    KeyThumbprint(Digest),
    /// Cert thumbprint digest (CBOR tag 559).
    #[cbor(tag = 559)]
    CertThumbprint(Digest),
    /// ASN.1 DER X.509 certificate (CBOR tag 562).
    #[cbor(tag = 562, bytes)]
    PkixAsn1DerCert(Vec<u8>),
}

/// Post-decode hook invoked by the `CborTagChoice` derive on
/// [`InstanceIdChoice`] to enforce the RFC 8392 §4 UEID size range
/// (7–33 bytes inclusive). Other variants pass through unchanged.
fn validate_instance_id_ueid(v: &InstanceIdChoice) -> Result<(), String> {
    if let InstanceIdChoice::Ueid(b) = v {
        if b.len() < 7 || b.len() > 33 {
            return Err(format!("UEID must be 7-33 bytes, got {}", b.len()));
        }
    }
    Ok(())
}

/// `$group-id-type-choice` — UUID or bytes.
///
/// Decoder accepts the same wire-format relaxations as [`ClassIdChoice`]:
///
/// - bare 16-byte `bstr` (no tag) is routed to [`Uuid`](Self::Uuid)
/// - any other bare `bstr` falls through to [`Bytes`](Self::Bytes)
///
/// Encoders always emit the tagged form (`#6.37` or `#6.560`).
#[derive(Clone, Debug, PartialEq, Eq, CborTagChoiceSerialize, CborTagChoiceDeserialize)]
#[non_exhaustive]
pub enum GroupIdChoice {
    /// UUID (CBOR tag 37). Bare 16-byte `bstr` is also accepted on decode.
    #[cbor(tag = 37, bytes, accept_bare = "uuid_16")]
    Uuid([u8; 16]),
    /// Generic tagged bytes (CBOR tag 560). Catch-all for any bare `bstr`
    /// not already routed to [`Uuid`](Self::Uuid).
    #[cbor(tag = 560, bytes, catch_bare_bytes)]
    Bytes(Vec<u8>),
}

/// `$measured-element-type-choice` — OID, UUID, uint, or text.
#[derive(Clone, Debug, PartialEq, Eq, CborTagChoiceSerialize, CborTagChoiceDeserialize)]
#[non_exhaustive]
pub enum MeasuredElement {
    /// OID (CBOR tag 111).
    #[cbor(tag = 111, bytes)]
    Oid(Vec<u8>),
    /// UUID (CBOR tag 37).
    #[cbor(tag = 37, bytes)]
    Uuid([u8; 16]),
    /// Unsigned integer.
    #[cbor(uint)]
    Uint(u64),
    /// Text string.
    #[cbor(text)]
    Text(String),
}

/// `$crypto-key-type-choice` — covers CBOR tags 554–562.
#[derive(Clone, Debug, PartialEq, Eq, CborTagChoiceSerialize, CborTagChoiceDeserialize)]
#[non_exhaustive]
pub enum CryptoKey {
    /// PEM SubjectPublicKeyInfo (CBOR tag 554).
    #[cbor(tag = 554, text)]
    PkixBase64Key(String),
    /// PEM X.509 certificate (CBOR tag 555).
    #[cbor(tag = 555, text)]
    PkixBase64Cert(String),
    /// PEM X.509 certificate chain (CBOR tag 556).
    #[cbor(tag = 556, text)]
    PkixBase64CertPath(String),
    /// Key thumbprint `[alg, val]` (CBOR tag 557).
    #[cbor(tag = 557)]
    KeyThumbprint(Digest),
    /// CBOR-encoded COSE_Key (CBOR tag 558).
    #[cbor(tag = 558, bytes)]
    CoseKey(Vec<u8>),
    /// Certificate thumbprint (CBOR tag 559).
    #[cbor(tag = 559)]
    CertThumbprint(Digest),
    /// Certification path thumbprint (CBOR tag 561).
    #[cbor(tag = 561)]
    CertPathThumbprint(Digest),
    /// ASN.1 DER X.509 certificate (CBOR tag 562).
    #[cbor(tag = 562, bytes)]
    PkixAsn1DerCert(Vec<u8>),
    /// Opaque key identifier (CBOR tag 560).
    #[cbor(tag = 560, bytes)]
    Bytes(Vec<u8>),
}

/// `linked-tag-map` — references another tag.
#[derive(Clone, Debug, PartialEq, CborSerialize, CborDeserialize)]
pub struct LinkedTagMap {
    /// `linked-tag-id` (key 0).
    #[cbor(key = 0)]
    pub linked_tag_id: TagIdChoice,
    /// `tag-rel` (key 1): supplements(0) or replaces(1).
    #[cbor(key = 1)]
    pub tag_rel: i64,
}

// ---------------------------------------------------------------------------
// From conversions for type-choice enums
// ---------------------------------------------------------------------------

impl From<String> for TagIdChoice {
    fn from(s: String) -> Self {
        Self::Text(s)
    }
}

impl From<&str> for TagIdChoice {
    fn from(s: &str) -> Self {
        Self::Text(s.to_owned())
    }
}

impl From<[u8; 16]> for TagIdChoice {
    fn from(u: [u8; 16]) -> Self {
        Self::Uuid(u)
    }
}

impl From<String> for MeasuredElement {
    fn from(s: String) -> Self {
        Self::Text(s)
    }
}

impl From<&str> for MeasuredElement {
    fn from(s: &str) -> Self {
        Self::Text(s.to_owned())
    }
}

impl From<u64> for MeasuredElement {
    fn from(n: u64) -> Self {
        Self::Uint(n)
    }
}

// ---------------------------------------------------------------------------
// Display impls
// ---------------------------------------------------------------------------

fn hex_short(bytes: &[u8]) -> String {
    if bytes.len() <= 16 {
        hex_encode(bytes)
    } else {
        format!("{}..({} bytes)", hex_encode(&bytes[..8]), bytes.len())
    }
}

fn hex_encode(bytes: &[u8]) -> String {
    bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

fn format_uuid(bytes: &[u8; 16]) -> String {
    format!(
        "{}-{}-{}-{}-{}",
        hex_encode(&bytes[0..4]),
        hex_encode(&bytes[4..6]),
        hex_encode(&bytes[6..8]),
        hex_encode(&bytes[8..10]),
        hex_encode(&bytes[10..16]),
    )
}

impl core::fmt::Display for TagIdChoice {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            TagIdChoice::Text(t) => write!(f, "{}", t),
            TagIdChoice::Uuid(u) => write!(f, "{}", format_uuid(u)),
        }
    }
}

impl core::fmt::Display for ClassIdChoice {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            ClassIdChoice::Oid(b) => write!(f, "oid:{}", hex_encode(b)),
            ClassIdChoice::Uuid(u) => write!(f, "{}", format_uuid(u)),
            ClassIdChoice::Bytes(b) => write!(f, "bytes:{}", hex_short(b)),
        }
    }
}

impl core::fmt::Display for InstanceIdChoice {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            InstanceIdChoice::Ueid(b) => write!(f, "ueid:{}", hex_short(b)),
            InstanceIdChoice::Uuid(u) => write!(f, "{}", format_uuid(u)),
            InstanceIdChoice::Bytes(b) => write!(f, "bytes:{}", hex_short(b)),
            InstanceIdChoice::PkixBase64Key(s) => write!(f, "pkix-key:{:.32}...", s),
            InstanceIdChoice::PkixBase64Cert(s) => write!(f, "pkix-cert:{:.32}...", s),
            InstanceIdChoice::CoseKey(b) => write!(f, "cose-key:({} bytes)", b.len()),
            InstanceIdChoice::KeyThumbprint(d) => {
                write!(f, "key-tp:alg={}:{}", d.alg(), hex_short(d.value()))
            }
            InstanceIdChoice::CertThumbprint(d) => {
                write!(f, "cert-tp:alg={}:{}", d.alg(), hex_short(d.value()))
            }
            InstanceIdChoice::PkixAsn1DerCert(b) => write!(f, "asn1-cert:({} bytes)", b.len()),
        }
    }
}

impl core::fmt::Display for GroupIdChoice {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            GroupIdChoice::Uuid(u) => write!(f, "{}", format_uuid(u)),
            GroupIdChoice::Bytes(b) => write!(f, "bytes:{}", hex_short(b)),
        }
    }
}

impl core::fmt::Display for MeasuredElement {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            MeasuredElement::Oid(b) => write!(f, "oid:{}", hex_encode(b)),
            MeasuredElement::Uuid(u) => write!(f, "{}", format_uuid(u)),
            MeasuredElement::Uint(n) => write!(f, "{}", n),
            MeasuredElement::Text(t) => write!(f, "{}", t),
        }
    }
}

impl core::fmt::Display for CryptoKey {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            CryptoKey::PkixBase64Key(s) => write!(f, "pkix-key:{:.40}...", s),
            CryptoKey::PkixBase64Cert(s) => write!(f, "pkix-cert:{:.40}...", s),
            CryptoKey::PkixBase64CertPath(s) => write!(f, "pkix-cert-path:{:.40}...", s),
            CryptoKey::KeyThumbprint(d) => {
                write!(f, "key-tp:alg={}:{}", d.alg(), hex_short(d.value()))
            }
            CryptoKey::CoseKey(b) => write!(f, "cose-key:({} bytes)", b.len()),
            CryptoKey::CertThumbprint(d) => {
                write!(f, "cert-tp:alg={}:{}", d.alg(), hex_short(d.value()))
            }
            CryptoKey::CertPathThumbprint(d) => {
                write!(f, "cert-path-tp:alg={}:{}", d.alg(), hex_short(d.value()))
            }
            CryptoKey::PkixAsn1DerCert(b) => write!(f, "asn1-cert:({} bytes)", b.len()),
            CryptoKey::Bytes(b) => write!(f, "bytes:{}", hex_short(b)),
        }
    }
}
