//! CoreDNS Client Implementation
//!
//! HTTP client for CoreDNS API for dynamic DNS management.

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use tracing::{debug, error};

/// DNS record types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DnsRecordType {
    A,     // IPv4 address
    AAAA,  // IPv6 address
    CNAME, // Canonical name
    TXT,   // Text record
}

impl std::fmt::Display for DnsRecordType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DnsRecordType::A => write!(f, "A"),
            DnsRecordType::AAAA => write!(f, "AAAA"),
            DnsRecordType::CNAME => write!(f, "CNAME"),
            DnsRecordType::TXT => write!(f, "TXT"),
        }
    }
}

/// DNS record structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DnsRecord {
    pub name: String,
    pub record_type: DnsRecordType,
    pub value: String,
    pub ttl: u32,
}

/// CoreDNS HTTP API client
pub struct CoreDnsClient {
    base_url: String,
    client: reqwest::Client,
}

impl CoreDnsClient {
    /// Create new CoreDNS client
    pub fn new(base_url: String) -> Self {
        Self {
            base_url,
            client: reqwest::Client::new(),
        }
    }

    /// Add DNS record
    pub async fn add_record(&self, record: DnsRecord) -> Result<()> {
        debug!("Adding DNS record: {:?}", record);

        let url = format!("{}/api/v1/records", self.base_url);

        let response = self
            .client
            .post(&url)
            .json(&record)
            .send()
            .await
            .context("Failed to send DNS add request")?;

        if response.status().is_success() {
            debug!("Successfully added DNS record: {}", record.name);
            Ok(())
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!(
                "Failed to add DNS record {}: {} - {}",
                record.name, status, error_text
            );

            anyhow::bail!("DNS add failed: {} - {}", status, error_text)
        }
    }

    /// Delete DNS record
    pub async fn delete_record(&self, name: &str) -> Result<()> {
        debug!("Deleting DNS record: {}", name);

        let url = format!("{}/api/v1/records/{}", self.base_url, name);

        let response = self
            .client
            .delete(&url)
            .send()
            .await
            .context("Failed to send DNS delete request")?;

        if response.status().is_success() {
            debug!("Successfully deleted DNS record: {}", name);
            Ok(())
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!(
                "Failed to delete DNS record {}: {} - {}",
                name, status, error_text
            );

            anyhow::bail!("DNS delete failed: {} - {}", status, error_text)
        }
    }

    /// Lookup DNS record
    pub async fn lookup_record(&self, name: &str) -> Result<Option<DnsRecord>> {
        debug!("Looking up DNS record: {}", name);

        let url = format!("{}/api/v1/records/{}", self.base_url, name);

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context("Failed to send DNS lookup request")?;

        if response.status().is_success() {
            let record: DnsRecord = response
                .json()
                .await
                .context("Failed to parse DNS record")?;

            debug!("Found DNS record: {:?}", record);
            Ok(Some(record))
        } else if response.status() == reqwest::StatusCode::NOT_FOUND {
            debug!("DNS record not found: {}", name);
            Ok(None)
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!(
                "Failed to lookup DNS record {}: {} - {}",
                name, status, error_text
            );

            anyhow::bail!("DNS lookup failed: {} - {}", status, error_text)
        }
    }

    /// List all DNS records
    pub async fn list_records(&self) -> Result<Vec<DnsRecord>> {
        debug!("Listing all DNS records");

        let url = format!("{}/api/v1/records", self.base_url);

        let response = self
            .client
            .get(&url)
            .send()
            .await
            .context("Failed to send DNS list request")?;

        if response.status().is_success() {
            let records: Vec<DnsRecord> = response
                .json()
                .await
                .context("Failed to parse DNS records")?;

            debug!("Found {} DNS records", records.len());
            Ok(records)
        } else {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            error!("Failed to list DNS records: {} - {}", status, error_text);

            anyhow::bail!("DNS list failed: {} - {}", status, error_text)
        }
    }

    /// Health check - verify CoreDNS is accessible
    pub async fn health_check(&self) -> Result<bool> {
        debug!("Checking CoreDNS health");

        let url = format!("{}/health", self.base_url);

        match self.client.get(&url).send().await {
            Ok(response) => {
                let is_healthy = response.status().is_success();
                debug!("CoreDNS health check: {}", is_healthy);
                Ok(is_healthy)
            }
            Err(e) => {
                error!("CoreDNS health check failed: {}", e);
                Ok(false)
            }
        }
    }
}

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

    #[test]
    fn test_dns_record_type_display() {
        assert_eq!(DnsRecordType::A.to_string(), "A");
        assert_eq!(DnsRecordType::AAAA.to_string(), "AAAA");
        assert_eq!(DnsRecordType::CNAME.to_string(), "CNAME");
        assert_eq!(DnsRecordType::TXT.to_string(), "TXT");
    }

    #[test]
    fn test_dns_record_creation() {
        let record = DnsRecord {
            name: "test.example.com".to_string(),
            record_type: DnsRecordType::A,
            value: "192.168.1.10".to_string(),
            ttl: 300,
        };

        assert_eq!(record.name, "test.example.com");
        assert_eq!(record.record_type, DnsRecordType::A);
        assert_eq!(record.value, "192.168.1.10");
        assert_eq!(record.ttl, 300);
    }

    #[test]
    fn test_coredns_client_creation() {
        let client = CoreDnsClient::new("http://localhost:53".to_string());
        assert_eq!(client.base_url, "http://localhost:53");
    }
}
