use crate::data_model::oid::OID_SHA256;

pub const CIR_VERSION_V1: u32 = 1;
pub const DIGEST_LEN_SHA256: usize = 32;

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

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

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CanonicalInputRepresentation {
    pub version: u32,
    pub hash_alg: CirHashAlgorithm,
    pub validation_time: time::OffsetDateTime,
    pub objects: Vec<CirObject>,
    pub tals: Vec<CirTal>,
}

impl CanonicalInputRepresentation {
    pub fn validate(&self) -> Result<(), String> {
        if self.version != CIR_VERSION_V1 {
            return Err(format!(
                "CIR version must be {CIR_VERSION_V1}, got {}",
                self.version
            ));
        }
        if !matches!(self.hash_alg, CirHashAlgorithm::Sha256) {
            return Err("CIR hashAlg must be SHA-256".into());
        }
        if self.validation_time.offset() != time::UtcOffset::UTC {
            return Err("CIR validationTime must be UTC".into());
        }
        validate_sorted_unique_strings(
            self.objects.iter().map(|item| item.rsync_uri.as_str()),
            "CIR.objects must be sorted by rsyncUri and unique",
        )?;
        validate_sorted_unique_strings(
            self.tals.iter().map(|item| item.tal_uri.as_str()),
            "CIR.tals must be sorted by talUri and unique",
        )?;
        if self.tals.is_empty() {
            return Err("CIR.tals must be non-empty".into());
        }
        for object in &self.objects {
            object.validate()?;
        }
        for tal in &self.tals {
            tal.validate()?;
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CirObject {
    pub rsync_uri: String,
    pub sha256: Vec<u8>,
}

impl CirObject {
    pub fn validate(&self) -> Result<(), String> {
        if !self.rsync_uri.starts_with("rsync://") {
            return Err(format!(
                "CirObject.rsync_uri must start with rsync://, got {}",
                self.rsync_uri
            ));
        }
        if self.sha256.len() != DIGEST_LEN_SHA256 {
            return Err(format!(
                "CirObject.sha256 must be {DIGEST_LEN_SHA256} bytes, got {}",
                self.sha256.len()
            ));
        }
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CirTal {
    pub tal_uri: String,
    pub tal_bytes: Vec<u8>,
}

impl CirTal {
    pub fn validate(&self) -> Result<(), String> {
        if !(self.tal_uri.starts_with("https://") || self.tal_uri.starts_with("http://")) {
            return Err(format!(
                "CirTal.tal_uri must start with http:// or https://, got {}",
                self.tal_uri
            ));
        }
        if self.tal_bytes.is_empty() {
            return Err("CirTal.tal_bytes must be non-empty".into());
        }
        Ok(())
    }
}

fn validate_sorted_unique_strings<'a>(
    items: impl IntoIterator<Item = &'a str>,
    message: &str,
) -> Result<(), String> {
    let mut prev: Option<&'a str> = None;
    for key in items {
        if let Some(prev_key) = prev && key <= prev_key {
            return Err(message.into());
        }
        prev = Some(key);
    }
    Ok(())
}
