//! DNS Record types

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::ZoneId;

/// Unique record identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RecordId(Uuid);

impl RecordId {
    /// Create a new random record ID
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from existing UUID
    pub fn from_uuid(id: Uuid) -> Self {
        Self(id)
    }

    /// Get the underlying UUID
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

impl Default for RecordId {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Display for RecordId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::str::FromStr for RecordId {
    type Err = uuid::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(Uuid::parse_str(s)?))
    }
}

/// Time-to-live in seconds
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Ttl(u32);

impl Ttl {
    /// Minimum TTL (1 second)
    pub const MIN: u32 = 1;
    /// Maximum TTL (1 week)
    pub const MAX: u32 = 604800;
    /// Default TTL (5 minutes)
    pub const DEFAULT: u32 = 300;

    /// Create a new TTL with validation
    pub fn new(seconds: u32) -> Result<Self, &'static str> {
        if seconds < Self::MIN {
            return Err("TTL must be at least 1 second");
        }
        if seconds > Self::MAX {
            return Err("TTL cannot exceed 1 week (604800 seconds)");
        }
        Ok(Self(seconds))
    }

    /// Get the TTL in seconds
    pub fn as_secs(&self) -> u32 {
        self.0
    }
}

impl Default for Ttl {
    fn default() -> Self {
        Self(Self::DEFAULT)
    }
}

impl std::fmt::Display for Ttl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// DNS record type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RecordType {
    /// IPv4 address
    A,
    /// IPv6 address
    Aaaa,
    /// Canonical name (alias)
    Cname,
    /// Mail exchange
    Mx,
    /// Text record
    Txt,
    /// Service record
    Srv,
    /// Nameserver
    Ns,
    /// Pointer (reverse DNS)
    Ptr,
    /// Certificate Authority Authorization
    Caa,
    /// Start of Authority (auto-generated for zones)
    Soa,
}

impl RecordType {
    /// Get the DNS type code
    pub fn type_code(&self) -> u16 {
        match self {
            RecordType::A => 1,
            RecordType::Aaaa => 28,
            RecordType::Cname => 5,
            RecordType::Mx => 15,
            RecordType::Txt => 16,
            RecordType::Srv => 33,
            RecordType::Ns => 2,
            RecordType::Ptr => 12,
            RecordType::Caa => 257,
            RecordType::Soa => 6,
        }
    }

    /// Create from DNS type code
    pub fn from_code(code: u16) -> Option<Self> {
        match code {
            1 => Some(RecordType::A),
            28 => Some(RecordType::Aaaa),
            5 => Some(RecordType::Cname),
            15 => Some(RecordType::Mx),
            16 => Some(RecordType::Txt),
            33 => Some(RecordType::Srv),
            2 => Some(RecordType::Ns),
            12 => Some(RecordType::Ptr),
            257 => Some(RecordType::Caa),
            6 => Some(RecordType::Soa),
            _ => None,
        }
    }
}

impl std::fmt::Display for RecordType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RecordType::A => write!(f, "A"),
            RecordType::Aaaa => write!(f, "AAAA"),
            RecordType::Cname => write!(f, "CNAME"),
            RecordType::Mx => write!(f, "MX"),
            RecordType::Txt => write!(f, "TXT"),
            RecordType::Srv => write!(f, "SRV"),
            RecordType::Ns => write!(f, "NS"),
            RecordType::Ptr => write!(f, "PTR"),
            RecordType::Caa => write!(f, "CAA"),
            RecordType::Soa => write!(f, "SOA"),
        }
    }
}

/// Record data variants
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum RecordData {
    /// A record: IPv4 address
    A { address: [u8; 4] },
    /// AAAA record: IPv6 address
    Aaaa { address: [u8; 16] },
    /// CNAME record: canonical name
    Cname { target: String },
    /// MX record: mail exchange
    Mx { preference: u16, exchange: String },
    /// TXT record: text data
    Txt { text: String },
    /// SRV record: service location
    Srv {
        priority: u16,
        weight: u16,
        port: u16,
        target: String,
    },
    /// NS record: nameserver
    Ns { nameserver: String },
    /// PTR record: pointer
    Ptr { target: String },
    /// CAA record: CA authorization
    Caa { flags: u8, tag: String, value: String },
}

impl RecordData {
    /// Get the record type for this data
    pub fn record_type(&self) -> RecordType {
        match self {
            RecordData::A { .. } => RecordType::A,
            RecordData::Aaaa { .. } => RecordType::Aaaa,
            RecordData::Cname { .. } => RecordType::Cname,
            RecordData::Mx { .. } => RecordType::Mx,
            RecordData::Txt { .. } => RecordType::Txt,
            RecordData::Srv { .. } => RecordType::Srv,
            RecordData::Ns { .. } => RecordType::Ns,
            RecordData::Ptr { .. } => RecordType::Ptr,
            RecordData::Caa { .. } => RecordType::Caa,
        }
    }

    /// Create A record from IPv4 string
    pub fn a_from_str(addr: &str) -> Result<Self, &'static str> {
        let parts: Vec<&str> = addr.split('.').collect();
        if parts.len() != 4 {
            return Err("invalid IPv4 address format");
        }
        let mut octets = [0u8; 4];
        for (i, part) in parts.iter().enumerate() {
            octets[i] = part.parse().map_err(|_| "invalid IPv4 octet")?;
        }
        Ok(RecordData::A { address: octets })
    }
}

/// A DNS record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Record {
    /// Unique record identifier
    pub id: RecordId,
    /// Zone this record belongs to
    pub zone_id: ZoneId,
    /// Record name (relative to zone, or @ for apex)
    pub name: String,
    /// Record type
    pub record_type: RecordType,
    /// Time to live
    pub ttl: Ttl,
    /// Record data
    pub data: RecordData,
    /// Is this record enabled?
    pub enabled: bool,
    /// Creation timestamp
    pub created_at: DateTime<Utc>,
    /// Last modified timestamp
    pub updated_at: DateTime<Utc>,
}

impl Record {
    /// Create a new record
    pub fn new(zone_id: ZoneId, name: impl Into<String>, data: RecordData) -> Self {
        let now = Utc::now();
        Self {
            id: RecordId::new(),
            zone_id,
            name: name.into(),
            record_type: data.record_type(),
            ttl: Ttl::default(),
            data,
            enabled: true,
            created_at: now,
            updated_at: now,
        }
    }

    /// Create a new record with specific TTL
    pub fn with_ttl(mut self, ttl: Ttl) -> Self {
        self.ttl = ttl;
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ttl_validation() {
        assert!(Ttl::new(300).is_ok());
        assert!(Ttl::new(0).is_err());
        assert!(Ttl::new(700000).is_err());
    }

    #[test]
    fn test_record_type_code() {
        assert_eq!(RecordType::A.type_code(), 1);
        assert_eq!(RecordType::Aaaa.type_code(), 28);
        assert_eq!(RecordType::from_code(1), Some(RecordType::A));
    }

    #[test]
    fn test_a_record_from_str() {
        let data = RecordData::a_from_str("192.168.1.1").unwrap();
        assert!(matches!(data, RecordData::A { address: [192, 168, 1, 1] }));
    }
}
