#![forbid(unsafe_code)]

//! Canonical `.qsig` detached signature container encoding.
//! Uses a fixed-position CBOR array to avoid map-ordering pitfalls.

use ciborium::{de, ser};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use serde_tuple::{Deserialize_tuple, Serialize_tuple};
use std::io;

/// Errors returned by encoding or decoding a `.qsig` structure.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("CBOR encode error: {0}")]
    Encode(String),
    #[error("CBOR decode error: {0}")]
    Decode(String),
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
}

/// Additional metadata about the signer (key identifier + optional opaque claims blob).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Signer {
    pub kid: String,
    #[serde(with = "serde_bytes")]
    #[serde(default)]
    pub claims: Vec<u8>,
}

/// Transparency log proof bundle (RFC 6962-style).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Transparency {
    #[serde(with = "serde_bytes")]
    pub root: Vec<u8>,
    #[serde(with = "serde_bytes")]
    pub proof: Vec<u8>,
}

/// Canonical `.qsig` representation. All fields have a fixed position.
#[derive(Debug, Clone, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)]
pub struct QSig {
    pub version: u8,
    pub alg: String,
    #[serde(with = "serde_bytes")]
    pub digest: Vec<u8>,
    #[serde(with = "serde_bytes")]
    pub sig: Vec<u8>,
    pub co_sig: Vec<ByteBuf>,
    pub tst: Option<ByteBuf>,
    pub tlog: Option<Transparency>,
    pub signer: Signer,
    pub time_unix: i64,
    pub domain_sep: String,
    #[serde(default)]
    pub meta: Option<ByteBuf>,
}

impl QSig {
    /// Serialize to canonical CBOR bytes.
    pub fn encode_to_vec(&self) -> Result<Vec<u8>, Error> {
        let mut buf = Vec::new();
        ser::into_writer(self, &mut buf).map_err(|e| Error::Encode(e.to_string()))?;
        Ok(buf)
    }

    /// Deserialize from CBOR bytes.
    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
        de::from_reader(bytes).map_err(|e| Error::Decode(e.to_string()))
    }

    /// Convenience constructor from raw field values.
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        version: u8,
        alg: String,
        digest: Vec<u8>,
        sig: Vec<u8>,
        co_sig: Vec<Vec<u8>>,
        tst: Option<Vec<u8>>,
        tlog: Option<Transparency>,
        signer: Signer,
        time_unix: i64,
        domain_sep: String,
        meta: Option<Vec<u8>>,
    ) -> Self {
        Self {
            version,
            alg,
            digest,
            sig,
            co_sig: co_sig.into_iter().map(ByteBuf::from).collect(),
            tst: tst.map(ByteBuf::from),
            tlog,
            signer,
            time_unix,
            domain_sep,
            meta: meta.map(ByteBuf::from),
        }
    }

    /// Access optional timestamp token as bytes.
    pub fn timestamp(&self) -> Option<&[u8]> {
        self.tst.as_ref().map(ByteBuf::as_ref)
    }

    /// Iterate over co-signature blobs as raw byte slices.
    pub fn co_signatures(&self) -> impl Iterator<Item = &[u8]> {
        self.co_sig.iter().map(ByteBuf::as_ref)
    }
}
