//! Domain checking functionality (RDAP + WHOIS)

use crate::error::{LazyDomainError, Result};
use crate::types::{CheckResult, Domain};
use std::time::Duration;
use trust_dns_resolver::TokioAsyncResolver;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};

/// RDAP client for domain availability checking
pub struct RdapClient {
    client: reqwest::Client,
    dns_resolver: TokioAsyncResolver,
}

impl RdapClient {
    /// Creates a new RDAP client
    pub fn new() -> Result<Self> {
        let client = reqwest::Client::builder()
            .timeout(Duration::from_secs(crate::constants::RDAP_TIMEOUT_SECS))
            .build()
            .map_err(|e| LazyDomainError::ConfigError(e.to_string()))?;

        // Create DNS resolver with default system configuration
        let dns_resolver = TokioAsyncResolver::tokio(
            ResolverConfig::default(),
            ResolverOpts::default()
        );

        Ok(RdapClient { client, dns_resolver })
    }

    /// Checks domain availability using RDAP
    pub async fn check_domain(&self, domain: &Domain) -> Result<CheckResult> {
        let fqdn = domain.to_fqdn();
        
        // Get the correct RDAP server for this TLD
        let rdap_url = match get_rdap_server(&domain.tld) {
            Some(base_url) => format!("{}/domain/{}", base_url, fqdn),
            None => {
                return Ok(CheckResult::error(
                    domain.name.clone(),
                    domain.tld.clone(),
                    format!("No RDAP server configured for .{}", domain.tld),
                ));
            }
        };
        
        match self.client.get(&rdap_url).send().await {
            Ok(response) => {
                if response.status().is_success() {
                    // Domain exists in RDAP = taken
                    // Try to extract registrar from response
                    let registrar = if let Ok(json) = response.json::<serde_json::Value>().await {
                        json.get("entities")
                            .and_then(|e| e.as_array())
                            .and_then(|arr| arr.first())
                            .and_then(|entity| entity.get("vcardArray"))
                            .and_then(|vcard| vcard.get(1))
                            .and_then(|props| props.as_array())
                            .and_then(|props| {
                                props.iter().find(|p| {
                                    p.as_array()
                                        .and_then(|arr| arr.first())
                                        .and_then(|v| v.as_str())
                                        == Some("fn")
                                })
                            })
                            .and_then(|prop| prop.get(3))
                            .and_then(|v| v.as_str())
                            .map(|s| s.to_string())
                    } else {
                        None
                    };
                    
                    Ok(CheckResult::taken(
                        domain.name.clone(),
                        domain.tld.clone(),
                        registrar,
                        None,
                    ))
                } else if response.status().as_u16() == 404 {
                    // 404 from RDAP might mean available, but verify with DNS
                    // Some RDAP servers have incomplete data
                    self.verify_with_dns(domain).await
                } else {
                    Ok(CheckResult::error(
                        domain.name.clone(),
                        domain.tld.clone(),
                        format!("RDAP returned status: {}", response.status()),
                    ))
                }
            }
            Err(e) => Ok(CheckResult::error(
                domain.name.clone(),
                domain.tld.clone(),
                format!("RDAP request failed: {}", e),
            )),
        }
    }

    /// Verifies domain availability using DNS resolution as a fallback
    /// 
    /// When RDAP returns 404 (not found), we double-check by attempting DNS resolution.
    /// If the domain has DNS records (A, AAAA, CNAME, MX, etc.), it's taken.
    /// If it doesn't resolve, it's truly available.
    async fn verify_with_dns(&self, domain: &Domain) -> Result<CheckResult> {
        let fqdn = domain.to_fqdn();
        
        // Try to resolve A records (IPv4) - most common record type
        // If we get any valid response, the domain is registered
        // DNS is fast - use shorter timeout than HTTP
        let lookup_result = tokio::time::timeout(
            Duration::from_secs(crate::constants::DNS_TIMEOUT_SECS),
            self.dns_resolver.lookup_ip(&fqdn)
        ).await;
        
        match lookup_result {
            Ok(Ok(response)) => {
                // Domain has DNS records! It's taken, even though RDAP returned 404
                let ip_count = response.iter().count();
                Ok(CheckResult::taken(
                    domain.name.clone(),
                    domain.tld.clone(),
                    Some(format!("DNS-verified ({} IP records)", ip_count)),
                    None,
                ))
            }
            Ok(Err(_)) | Err(_) => {
                // No DNS records found = truly available
                // (Timeout or NXDOMAIN both mean the domain doesn't resolve)
                Ok(CheckResult::available(domain.name.clone(), domain.tld.clone()))
            }
        }
    }
}

/// Get the RDAP server URL for a given TLD
fn get_rdap_server(tld: &str) -> Option<String> {
    let tld_lower = tld.to_lowercase();
    match tld_lower.as_str() {
        // Verisign TLDs - each has its own endpoint path
        "com" => Some("https://rdap.verisign.com/com/v1".to_string()),
        "net" => Some("https://rdap.verisign.com/net/v1".to_string()),
        
        // Identity Digital (formerly Donuts) - many new gTLDs
        "io" | "co" | "app" | "dev" | "tech" | "cloud" | "digital" | "ai" | 
        "xyz" | "online" | "site" | "store" | "shop" | "club" | "live" |
        "solutions" | "services" | "consulting" | "management" | "ventures" |
        "codes" | "design" | "studio" | "agency" | "media" | "photo" | "video" => {
            Some("https://rdap.identitydigital.services/rdap".to_string())
        },
        
        // Public Interest Registry (.org)
        "org" => Some("https://rdap.publicinterestregistry.org/rdap".to_string()),
        
        // Afilias (.info, .mobi, .pro)
        "info" | "mobi" | "pro" => Some("https://rdap.afilias.net/rdap/afilias".to_string()),
        
        // NeuStar (.biz, .us)
        "biz" | "us" => Some("https://rdap.nic.biz".to_string()),
        
        // .name
        "name" => Some("https://rdap.nic.name".to_string()),
        
        // Fallback: no known RDAP server
        _ => None,
    }
}

impl Default for RdapClient {
    fn default() -> Self {
        Self::new().expect("Failed to create RDAP client")
    }
}

/// WHOIS client for domain availability checking (fallback)
#[allow(dead_code)]
pub struct WhoisClient;

impl WhoisClient {
    /// Creates a new WHOIS client
    #[allow(dead_code)]
    pub fn new() -> Self {
        WhoisClient
    }

    /// Checks domain availability using WHOIS
    #[allow(dead_code)]
    pub async fn check_domain(&self, domain: &Domain) -> Result<CheckResult> {
        // Simplified MVP: mark as error with message to implement WHOIS
        Ok(CheckResult::error(
            domain.name.clone(),
            domain.tld.clone(),
            "WHOIS checking not yet implemented - use RDAP",
        ))
    }
}

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

/// Batch checker for checking multiple domains concurrently
#[allow(dead_code)]
pub struct BatchChecker {
    rdap_client: RdapClient,
    whois_client: WhoisClient,
    concurrency: usize,
}

impl BatchChecker {
    /// Creates a new batch checker
    #[allow(dead_code)]
    pub fn new(concurrency: usize) -> Result<Self> {
        Ok(BatchChecker {
            rdap_client: RdapClient::new()?,
            whois_client: WhoisClient::new(),
            concurrency,
        })
    }

    /// Checks multiple domains concurrently
    #[allow(dead_code)]
    pub async fn check_domains(&self, domains: Vec<Domain>) -> Vec<CheckResult> {
        let mut results = Vec::new();
        
        // Simplified MVP: sequential checking (concurrent will be added post-MVP)
        for domain in domains {
            let result = self.rdap_client.check_domain(&domain).await
                .unwrap_or_else(|_| CheckResult::error(
                    domain.name.clone(),
                    domain.tld.clone(),
                    "Check failed",
                ));
            results.push(result);
        }
        
        results
    }
}
