//! Domain Verification - Custom domain ownership proof
//!
//! Verifies that users own the domains they want to link to their names
//! using DNS-based verification (CNAME or TXT records).

use crate::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Verification method for custom domains
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VerificationMethod {
    /// CNAME record pointing to name.synor.network
    Cname,
    /// TXT record with verification token
    Txt,
}

/// Domain verification status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VerificationStatus {
    /// Verification pending
    Pending,
    /// Domain verified
    Verified,
    /// Verification failed
    Failed,
    /// Verification expired (needs re-verification)
    Expired,
}

/// Domain record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainRecord {
    /// The custom domain
    pub domain: String,
    /// Associated Synor name
    pub name: String,
    /// Verification method
    pub method: VerificationMethod,
    /// Verification token (for TXT method)
    pub token: String,
    /// Current status
    pub status: VerificationStatus,
    /// Block when verification was requested
    pub requested_at: u64,
    /// Block when verified (0 if not verified)
    pub verified_at: u64,
    /// Block when verification expires (0 = never)
    pub expires_at: u64,
}

/// Domain verifier
pub struct DomainVerifier {
    /// Pending verifications
    pending: HashMap<String, DomainRecord>,
    /// Verified domains
    verified: HashMap<String, DomainRecord>,
    /// Synor hosting domain suffix
    hosting_domain: String,
    /// Verification token prefix
    token_prefix: String,
    /// Verification expiry in blocks
    verification_expiry: u64,
}

impl DomainVerifier {
    /// Create a new domain verifier
    pub fn new(hosting_domain: String) -> Self {
        Self {
            pending: HashMap::new(),
            verified: HashMap::new(),
            hosting_domain,
            token_prefix: "synor-verify=".to_string(),
            verification_expiry: 5_256_000, // ~1 year at 6s blocks
        }
    }

    /// Request verification for a domain
    pub fn request_verification(
        &mut self,
        domain: String,
        name: String,
        method: VerificationMethod,
        current_block: u64,
    ) -> Result<DomainRecord> {
        // Validate domain format
        self.validate_domain(&domain)?;

        // Check if already verified
        if self.verified.contains_key(&domain) {
            return Err(Error::DomainAlreadyLinked(domain));
        }

        // Generate verification token
        let token = self.generate_token(&domain, &name, current_block);

        let record = DomainRecord {
            domain: domain.clone(),
            name,
            method,
            token,
            status: VerificationStatus::Pending,
            requested_at: current_block,
            verified_at: 0,
            expires_at: 0,
        };

        self.pending.insert(domain, record.clone());

        Ok(record)
    }

    /// Get verification instructions for a domain
    pub fn get_instructions(&self, domain: &str) -> Result<VerificationInstructions> {
        let record = self.pending.get(domain)
            .ok_or_else(|| Error::DomainNotFound(domain.to_string()))?;

        match record.method {
            VerificationMethod::Cname => {
                Ok(VerificationInstructions {
                    method: VerificationMethod::Cname,
                    record_type: "CNAME".to_string(),
                    record_name: record.domain.clone(),
                    record_value: format!("{}.{}", record.name, self.hosting_domain),
                    instructions: format!(
                        "Add a CNAME record:\n  {} CNAME {}.{}",
                        record.domain, record.name, self.hosting_domain
                    ),
                })
            }
            VerificationMethod::Txt => {
                Ok(VerificationInstructions {
                    method: VerificationMethod::Txt,
                    record_type: "TXT".to_string(),
                    record_name: format!("_synor.{}", record.domain),
                    record_value: format!("{}{}", self.token_prefix, record.token),
                    instructions: format!(
                        "Add a TXT record:\n  _synor.{} TXT \"{}{}\"",
                        record.domain, self.token_prefix, record.token
                    ),
                })
            }
        }
    }

    /// Verify a domain (check DNS records)
    pub fn verify(&mut self, domain: &str, current_block: u64) -> Result<DomainRecord> {
        let record = self.pending.get(domain)
            .ok_or_else(|| Error::DomainNotFound(domain.to_string()))?
            .clone();

        // In production, this would do actual DNS lookups
        // For now, we'll simulate verification
        let verified = self.check_dns(&record)?;

        if verified {
            let mut verified_record = record;
            verified_record.status = VerificationStatus::Verified;
            verified_record.verified_at = current_block;
            verified_record.expires_at = current_block + self.verification_expiry;

            self.pending.remove(domain);
            self.verified.insert(domain.to_string(), verified_record.clone());

            Ok(verified_record)
        } else {
            Err(Error::VerificationFailed(
                domain.to_string(),
                "DNS record not found or doesn't match".into(),
            ))
        }
    }

    /// Check if a domain is verified
    pub fn is_verified(&self, domain: &str, current_block: u64) -> bool {
        self.verified.get(domain).map_or(false, |r| {
            r.status == VerificationStatus::Verified
                && (r.expires_at == 0 || current_block <= r.expires_at)
        })
    }

    /// Get verified domain record
    pub fn get_verified(&self, domain: &str) -> Option<&DomainRecord> {
        self.verified.get(domain)
    }

    /// Remove verification for a domain
    pub fn remove(&mut self, domain: &str) -> bool {
        self.pending.remove(domain).is_some() || self.verified.remove(domain).is_some()
    }

    /// Validate domain format
    fn validate_domain(&self, domain: &str) -> Result<()> {
        // Basic domain validation
        if domain.is_empty() {
            return Err(Error::InvalidDomain("Domain cannot be empty".into()));
        }

        if domain.len() > 253 {
            return Err(Error::InvalidDomain("Domain too long".into()));
        }

        // Check each label
        for label in domain.split('.') {
            if label.is_empty() || label.len() > 63 {
                return Err(Error::InvalidDomain("Invalid label length".into()));
            }

            if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
                return Err(Error::InvalidDomain("Invalid characters in domain".into()));
            }

            if label.starts_with('-') || label.ends_with('-') {
                return Err(Error::InvalidDomain("Label cannot start or end with hyphen".into()));
            }
        }

        // Must have at least one dot (not just a TLD)
        if !domain.contains('.') {
            return Err(Error::InvalidDomain("Domain must have at least one dot".into()));
        }

        Ok(())
    }

    /// Generate verification token
    fn generate_token(&self, domain: &str, name: &str, block: u64) -> String {
        let input = format!("{}:{}:{}", domain, name, block);
        let hash = blake3::hash(input.as_bytes());
        hex::encode(&hash.as_bytes()[..16]) // 32 char hex token
    }

    /// Check DNS records (stub - real implementation would use DNS resolver)
    #[cfg(not(feature = "dns"))]
    fn check_dns(&self, _record: &DomainRecord) -> Result<bool> {
        // Without DNS feature, always return true (for testing)
        Ok(true)
    }

    /// Check DNS records (real implementation with trust-dns)
    #[cfg(feature = "dns")]
    fn check_dns(&self, record: &DomainRecord) -> Result<bool> {
        use trust_dns_resolver::Resolver;
        use trust_dns_resolver::config::*;

        let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default())
            .map_err(|e| Error::Dns(e.to_string()))?;

        match record.method {
            VerificationMethod::Cname => {
                let expected = format!("{}.{}.", record.name, self.hosting_domain);
                let response = resolver.lookup(&record.domain, RecordType::CNAME)
                    .map_err(|e| Error::Dns(e.to_string()))?;

                for cname in response.iter() {
                    if cname.to_string() == expected {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
            VerificationMethod::Txt => {
                let txt_domain = format!("_synor.{}", record.domain);
                let expected = format!("{}{}", self.token_prefix, record.token);

                let response = resolver.txt_lookup(&txt_domain)
                    .map_err(|e| Error::Dns(e.to_string()))?;

                for txt in response.iter() {
                    let txt_str = txt.to_string();
                    if txt_str.contains(&expected) {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
        }
    }
}

/// Verification instructions for users
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationInstructions {
    /// Verification method
    pub method: VerificationMethod,
    /// DNS record type to add
    pub record_type: String,
    /// DNS record name
    pub record_name: String,
    /// DNS record value
    pub record_value: String,
    /// Human-readable instructions
    pub instructions: String,
}

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

    #[test]
    fn test_request_verification() {
        let mut verifier = DomainVerifier::new("synor.network".to_string());

        let record = verifier
            .request_verification(
                "myapp.com".to_string(),
                "myapp".to_string(),
                VerificationMethod::Cname,
                0,
            )
            .unwrap();

        assert_eq!(record.domain, "myapp.com");
        assert_eq!(record.name, "myapp");
        assert_eq!(record.status, VerificationStatus::Pending);
    }

    #[test]
    fn test_get_instructions() {
        let mut verifier = DomainVerifier::new("synor.network".to_string());

        verifier
            .request_verification(
                "myapp.com".to_string(),
                "myapp".to_string(),
                VerificationMethod::Cname,
                0,
            )
            .unwrap();

        let instructions = verifier.get_instructions("myapp.com").unwrap();
        assert_eq!(instructions.record_type, "CNAME");
        assert!(instructions.record_value.contains("myapp.synor.network"));
    }

    #[test]
    fn test_verify_domain() {
        let mut verifier = DomainVerifier::new("synor.network".to_string());

        verifier
            .request_verification(
                "myapp.com".to_string(),
                "myapp".to_string(),
                VerificationMethod::Cname,
                0,
            )
            .unwrap();

        // Without DNS feature, verify always succeeds
        let record = verifier.verify("myapp.com", 100).unwrap();
        assert_eq!(record.status, VerificationStatus::Verified);
        assert!(verifier.is_verified("myapp.com", 100));
    }

    #[test]
    fn test_invalid_domain() {
        let mut verifier = DomainVerifier::new("synor.network".to_string());

        // No TLD
        assert!(verifier
            .request_verification("myapp".to_string(), "myapp".to_string(), VerificationMethod::Cname, 0)
            .is_err());

        // Empty
        assert!(verifier
            .request_verification("".to_string(), "myapp".to_string(), VerificationMethod::Cname, 0)
            .is_err());
    }
}
