// ## 📂 File: `src/headers/types.rs`

//! headers/types.rs
//! Core header struct and typed views.
//!
//! Industry notes:
//! - #[repr(C)] + fixed field sizes ensures binary stability across Rust and FFI.
//! - Use little-endian when writing/reading multi-byte integers for cross-language parity.
//! - Reserved bytes allow future fields without changing size; always zero them.
//! - This header is 80 bytes, fixed length, designed for reproducibility and forward compatibility.

use std::fmt;
use num_enum::TryFromPrimitive;

use crate::compression::CodecError;
use crate::compression::CompressionCodec;
use crate::constants::aad_domain_ids;
use crate::constants::alg_profile_ids;
use crate::constants::strategy_ids;
use crate::constants::{HEADER_V1, MAGIC_RSE1, DEFAULT_CHUNK_SIZE, MAX_CHUNK_SIZE};
use crate::constants::{cipher_ids, prf_ids, flags};
use crate::utils::enum_name_or_hex;
use crate::utils::fmt_bytes;

/// Strategy choices for encoder metadata (decoder may still parallelize).
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive)]
pub enum Strategy {
    Auto       = strategy_ids::AUTO,
    Sequential = strategy_ids::SEQUENTIAL,
    Parallel   = strategy_ids::PARALLEL,
}
impl Strategy {
    pub fn from(raw: u16) -> Result<Self, HeaderError> {
        match raw {
            x if x == Strategy::Sequential as u16    => Ok(Strategy::Sequential),
            x if x == Strategy::Parallel as u16    => Ok(Strategy::Parallel),
            x if x == Strategy::Auto as u16     => Ok(Strategy::Auto),
            _ => Err(HeaderError::UnknownStrategy { raw }),
        }
    }
    pub fn verify(raw: u16) -> Result<(), HeaderError> {
        match raw {
            x if x == Strategy::Sequential as u16   => Ok(()),
            x if x == Strategy::Parallel as u16     => Ok(()),
            x if x == Strategy::Auto as u16         => Ok(()),
            _ => Err(HeaderError::UnknownStrategy { raw }),
        }
    }
}

/// Cipher suites (header registry).
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive)]
pub enum CipherSuite {
    Aes256Gcm        = cipher_ids::AES256_GCM,
    Chacha20Poly1305 = cipher_ids::CHACHA20_POLY1305,

}
impl CipherSuite {
    pub fn verify(raw: u16) -> Result<(), HeaderError> {
        match raw {
            x if x == CipherSuite::Aes256Gcm as u16        => Ok(()),
            x if x == CipherSuite::Chacha20Poly1305 as u16 => Ok(()),
            _ => Err(HeaderError::UnknownCipherSuite { raw }),
        }
    }
}

/// HKDF PRF choices (header registry).
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive)]
pub enum HkdfPrf {
    Sha256  = prf_ids::SHA256,
    Sha512  = prf_ids::SHA512,
    Sha3_256= prf_ids::SHA3_256,
    Sha3_512= prf_ids::SHA3_512,
    Blake3K = prf_ids::BLAKE3K,
}
impl HkdfPrf {
    pub fn verify(raw: u16) -> Result<(), HeaderError> {
        match raw {
            x if x == HkdfPrf::Sha256 as u16  => Ok(()),
            x if x == HkdfPrf::Sha512 as u16  => Ok(()),
            x if x == HkdfPrf::Sha3_256 as u16  => Ok(()),
            x if x == HkdfPrf::Sha3_512 as u16  => Ok(()),
            x if x == HkdfPrf::Blake3K as u16 => Ok(()),
            _ => Err(HeaderError::UnknownHkdfPrf { raw }),
        }
    }
}

/// Algorithm profile bundles cipher + PRF combinations.
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive)]
pub enum AlgProfile {
    Aes256GcmHkdfSha256         = alg_profile_ids::AES256_GCM_HKDF_SHA256,
    Aes256GcmHkdfSha512         = alg_profile_ids::AES256_GCM_HKDF_SHA512,
    Chacha20Poly1305HkdfSha256  = alg_profile_ids::CHACHA20_POLY1305_HKDF_SHA256,
    Chacha20Poly1305HkdfSha512  = alg_profile_ids::CHACHA20_POLY1305_HKDF_SHA512,
    Chacha20Poly1305HkdfBlake3K = alg_profile_ids::CHACHA20_POLY1305_HKDF_BLAKE3K,
}

impl AlgProfile {
    pub fn verify(raw: u16) -> Result<(), HeaderError> {
        match raw {
            x if x == AlgProfile::Aes256GcmHkdfSha256 as u16         => Ok(()),
            x if x == AlgProfile::Aes256GcmHkdfSha512 as u16         => Ok(()),
            x if x == AlgProfile::Chacha20Poly1305HkdfSha256 as u16  => Ok(()),
            x if x == AlgProfile::Chacha20Poly1305HkdfSha512 as u16  => Ok(()),
            x if x == AlgProfile::Chacha20Poly1305HkdfBlake3K as u16 => Ok(()),
            _ => Err(HeaderError::UnknownAlgProfile { raw }),
        }
    }
}

/// AAD domain identifiers.
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive)]
pub enum AadDomain {
    Generic      = aad_domain_ids::GENERIC,
    FileEnvelope = aad_domain_ids::FILE_ENVELOPE,
    PipeEnvelope = aad_domain_ids::PIPE_ENVELOPE,
}

impl AadDomain {
    pub fn verify(raw: u16) -> Result<(), HeaderError> {
        match raw {
            x if x == AadDomain::Generic as u16      => Ok(()),
            x if x == AadDomain::FileEnvelope as u16 => Ok(()),
            x if x == AadDomain::PipeEnvelope as u16 => Ok(()),
            _ => Err(HeaderError::UnknownAadDomain { raw }),
        }
    }
}

/// Core Rust header type used internally by pipelines.
/// - Fixed-size fields ensure deterministic wire format.
/// - Salt provides per-stream nonce uniqueness.
/// - Reserved bytes allow safe extension without breaking ABI.
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct HeaderV1 {
    pub magic: [u8; 4],        // "RSE1" magic marker
    pub version: u16,          // protocol version
    pub alg_profile: u16,      // bundle id (cipher + PRF choice)
    pub cipher: u16,           // cipher enum (cipher_ids)
    pub hkdf_prf: u16,         // PRF enum (prf_ids)
    pub compression: u16,      // compression enum (codec_ids)
    pub strategy: u16,         // sequential / parallel / auto
    pub aad_domain: u16,       // binds header semantics in AAD
    pub flags: u16,            // presence and behavior bits
    pub chunk_size: u32,       // frame plaintext split target size
    pub plaintext_size: u64,   // optional; 0 if unknown
    pub crc32: u32,            // optional; 0 if not provided
    pub dict_id: u32,          // optional compression dictionary id
    pub salt: [u8; 16],        // nonce base (random per stream)
    pub key_id: u32,           // master key registry reference
    pub parallel_hint: u32,    // optional suggested worker count
    pub enc_time_ns: u64,      // optional monotonic encoding timestamp
    pub reserved: [u8; 8],     // future fields; must be zero
}

impl Default for HeaderV1 {
    /// Provides a default header with sane values:
    /// - Magic set to "RSE1"
    /// - Default cipher: Chacha20-Poly1305
    /// - Default PRF: HKDF-BLAKE3
    /// - Default compression: Auto
    /// - Default strategy: Auto
    /// - Default chunk size: DEFAULT_CHUNK_SIZE KiB
    /// - Optional fields zeroed
    fn default() -> Self {
        Self {
            magic: MAGIC_RSE1,
            version: HEADER_V1,
            alg_profile: AlgProfile::Aes256GcmHkdfSha256 as u16,
            cipher: CipherSuite::Chacha20Poly1305 as u16,
            hkdf_prf: HkdfPrf::Blake3K as u16,
            compression: CompressionCodec::Auto as u16,
            strategy: Strategy::Auto as u16,
            aad_domain: AadDomain::Generic as u16,
            flags: 0,
            chunk_size: DEFAULT_CHUNK_SIZE as u32,        // 64 KiB default
            plaintext_size: 0,
            crc32: 0,
            dict_id: 0,
            salt: [1u8; 16],
            key_id: 0,
            parallel_hint: 0,
            enc_time_ns: 0,
            reserved: [0u8; 8],
        }
    }
}


impl HeaderV1 {
    /// Fixed header size in bytes.
    pub const LEN: usize = 80; 

    /// Canonical header for tests.
    /// Guaranteed to pass `validate()` unless a regression is introduced.
    pub fn test_header() -> Self {
        Self {
            magic: MAGIC_RSE1,
            version: HEADER_V1,
            alg_profile: AlgProfile::Chacha20Poly1305HkdfBlake3K as u16,
            cipher: CipherSuite::Chacha20Poly1305 as u16,
            hkdf_prf: HkdfPrf::Blake3K as u16,
            compression: CompressionCodec::Auto as u16,
            strategy: Strategy::Auto as u16,
            aad_domain: AadDomain::Generic as u16,
            flags: 0,
            chunk_size: DEFAULT_CHUNK_SIZE as u32,
            plaintext_size: 0,
            crc32: 0,
            dict_id: 0,
            salt: [0xA5; 16],
            key_id: 1,
            parallel_hint: 0,
            enc_time_ns: 0,
            reserved: [0u8; 8],
        }
    }

    pub fn validate(&self) -> Result<(), HeaderError> {
        // Magic
        if self.magic != MAGIC_RSE1 {
            return Err(HeaderError::InvalidMagic {
                have: self.magic,
                need: MAGIC_RSE1,
            });
        }

        // Version
        if self.version == 0 {
            return Err(HeaderError::InvalidVersion { have: self.version });
        }

        // Chunk size
        if self.chunk_size == 0 {
            return Err(HeaderError::InvalidChunkSizeZero);
        }
        if self.chunk_size > MAX_CHUNK_SIZE as u32 {
            return Err(HeaderError::InvalidChunkSizeTooLarge {
                have: self.chunk_size,
                max: MAX_CHUNK_SIZE as u32,
            });
        }

        // Enums
        AlgProfile::verify(self.alg_profile)?;
        CipherSuite::verify(self.cipher)?;
        HkdfPrf::verify(self.hkdf_prf)?;
        Strategy::verify(self.strategy)?;
        AadDomain::verify(self.aad_domain)?;

        // Compression codec
        CompressionCodec::verify(self.compression)?;

        // Salt must not be all zero
        if self.salt.iter().all(|&b| b == 0) {
            return Err(HeaderError::InvalidSalt { salt: self.salt });
        }

        // Reserved bytes must be zero
        if self.reserved.iter().any(|&b| b != 0) {
            return Err(HeaderError::ReservedBytesNonZero {
                reserved: self.reserved,
            });
        }

        // Dict flag consistency
        if (self.flags & flags::DICT_USED) != 0 && self.dict_id == 0 {
            return Err(HeaderError::DictUsedButMissingId);
        }

        Ok(())
    }

    /// Initialize a header with mandatory fields and caller-provided random salt.
    /// - master_key linkage via key_id is set by caller.
    /// - Optional fields (plaintext_size, crc32) are left zero unless flags are set.
    ///
    /// Industry note: per-stream random salt ensures nonce uniqueness across streams.
    pub fn new_with_salt(salt: [u8; 16]) -> Self {
        Self { salt, ..Default::default() }
    }

    /// Marks plaintext_size as present, sets value and flag.
    pub fn set_plaintext_size(&mut self, size: u64) {
        self.plaintext_size = size;
        self.flags |= flags::HAS_TOTAL_LEN;
    }

    /// Marks crc32 as present, sets value and flag.
    pub fn set_crc32(&mut self, crc32: u32) {
        self.crc32 = crc32;
        self.flags |= flags::HAS_CRC32;
    }

    /// Marks dict_id as used.
    pub fn set_dict_id(&mut self, dict_id: u32) {
        self.dict_id = dict_id;
        self.flags |= flags::DICT_USED;
    }

    /// Enables authenticated terminator frame expectation.
    pub fn enable_terminator(&mut self) {
        self.flags |= flags::HAS_TERMINATOR;
    }

    /// Enables authenticated final digest frame expectation.
    pub fn enable_final_digest(&mut self) {
        self.flags |= flags::HAS_FINAL_DIGEST;
    }

    /// Strict AAD domain enforcement (decoder must match).
    pub fn enable_aad_strict(&mut self) {
        self.flags |= flags::AAD_STRICT;
    }
}

#[derive(Debug, Clone)]
pub enum HeaderError {
    /// Buffer too short to contain a minimal header.
    BufferTooShort { have: usize, need: usize },

    /// Invalid magic marker (expected "RSE1").
    InvalidMagic { have: [u8; 4], need: [u8; 4] },

    /// Invalid crc32 marker (expected "RSE1").
    InvalidCrc32 { have: usize, need: usize },

    /// Invalid version (e.g., zero or unsupported).
    InvalidVersion { have: u16 },

    /// Unknown or unsupported cipher suite.
    UnknownCipherSuite { raw: u16 },

    /// Unknown or unsupported HKDF PRF.
    UnknownHkdfPrf { raw: u16 },

    /// Unknown or unsupported compression codec.
    UnknownCompression { raw: u16 },

    /// Unknown or unsupported strategy.
    UnknownStrategy { raw: u16 },

    /// Unknown or unsupported algorithm profile.
    UnknownAlgProfile { raw: u16 },

    /// Unknown or unsupported AAD domain.
    UnknownAadDomain { raw: u16 },

    /// Salt is invalid (e.g., all zeros).
    InvalidSalt { salt: [u8; 16] },

    /// Invalid chunk size (zero).
    InvalidChunkSizeZero,

    /// Invalid chunk size (too large).
    InvalidChunkSizeTooLarge { have: u32, max: u32 },

    /// Reserved bytes must be zero.
    ReservedBytesNonZero { reserved: [u8; 8] },

    /// Flags indicate dictionary used but dict_id is zero.
    DictUsedButMissingId,
    
    /// Generic validation error with context.
    Validation(String),
}

impl fmt::Display for HeaderError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use HeaderError::*;
        match self {
            BufferTooShort { have, need } =>
                write!(f, "header buffer too short: {} < {}", have, need),
            InvalidMagic { have, need  } =>
                write!(f, "invalid magic: expected {}, got {}", fmt_bytes(need), fmt_bytes(have)),
            InvalidCrc32 { have, need  } =>
                write!(f, "invalid crc32: expected {}, got {}", need, have),

            InvalidVersion { have } =>
                write!(f, "invalid version: {}", have),

            UnknownCipherSuite { raw } =>
                write!(f, "unknown cipher suite: {}",
                    enum_name_or_hex::<CipherSuite>(*raw)),
            UnknownHkdfPrf { raw } =>
                write!(f, "unknown HKDF PRF: {}",
                    enum_name_or_hex::<HkdfPrf>(*raw)),
            UnknownCompression { raw } =>
                write!(f, "unknown compression: {}",
                    enum_name_or_hex::<CompressionCodec>(*raw)),
            UnknownStrategy { raw } =>
                write!(f, "unknown strategy: {}",
                    enum_name_or_hex::<Strategy>(*raw)),
            UnknownAlgProfile { raw } =>
                write!(f, "unknown algorithm profile: {}",
                    enum_name_or_hex::<AlgProfile>(*raw)),
            UnknownAadDomain { raw } =>
                write!(f, "unknown AAD domain: {}",
                    enum_name_or_hex::<AadDomain>(*raw)),

            InvalidSalt { salt } =>
                write!(f, "invalid salt: all zeros ({})", fmt_bytes(salt)),
            InvalidChunkSizeZero =>
                write!(f, "invalid chunk_size: zero"),
            InvalidChunkSizeTooLarge { have, max } =>
                write!(f, "invalid chunk_size: {} > {}", have, max),

            ReservedBytesNonZero { reserved } =>
                write!(f, "reserved bytes must be zero, got {}", fmt_bytes(reserved)),
            DictUsedButMissingId =>
                write!(f, "DICT_USED flag set but dict_id is zero"),
            Validation(msg) =>
                write!(f, "header validation error: {}", msg),
        }
    }
}

/// Allow `?` on std::io::Error
impl From<std::io::Error> for HeaderError {
    fn from(e: std::io::Error) -> Self {
        HeaderError::Validation(e.to_string())
    }
}
impl From<CodecError> for HeaderError {
    fn from(e: CodecError) -> Self {
        match e {
            CodecError::UnknownCompression { raw } => HeaderError::UnknownCompression { raw },
        }
    }
}

impl std::error::Error for HeaderError {}
