//! Name Registry - On-chain name to CID mapping
//!
//! Manages the registration and resolution of human-readable names
//! to content identifiers (CIDs) on the Synor blockchain.

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

/// Address type (32 bytes)
pub type Address = [u8; 32];

/// Name record stored on-chain
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NameRecord {
    /// The registered name
    pub name: String,
    /// Owner address
    pub owner: Address,
    /// Current CID the name points to
    pub cid: ContentId,
    /// Block number when registered
    pub registered_at: u64,
    /// Block number when expires (0 = never)
    pub expires_at: u64,
    /// Custom domains linked to this name
    pub custom_domains: Vec<String>,
    /// Metadata (optional JSON)
    pub metadata: Option<String>,
}

/// Request to register a new name
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistrationRequest {
    /// Name to register
    pub name: String,
    /// Initial CID
    pub cid: ContentId,
    /// Registration duration in blocks (0 = permanent)
    pub duration: u64,
    /// Optional metadata
    pub metadata: Option<String>,
}

/// Name registry state
#[derive(Debug, Default)]
pub struct NameRegistry {
    /// Name to record mapping
    records: HashMap<String, NameRecord>,
    /// Custom domain to name mapping
    domain_to_name: HashMap<String, String>,
    /// Registration fee (atomic SYNOR)
    registration_fee: u64,
    /// Annual renewal fee (atomic SYNOR)
    renewal_fee: u64,
    /// Grace period in blocks after expiry
    grace_period: u64,
}

impl NameRegistry {
    /// Create a new name registry
    pub fn new() -> Self {
        Self {
            records: HashMap::new(),
            domain_to_name: HashMap::new(),
            registration_fee: 10_000_000_000, // 10 SYNOR
            renewal_fee: 5_000_000_000,       // 5 SYNOR
            grace_period: 432_000,             // ~30 days at 6s blocks
        }
    }

    /// Create registry with custom fees
    pub fn with_fees(registration_fee: u64, renewal_fee: u64, grace_period: u64) -> Self {
        Self {
            records: HashMap::new(),
            domain_to_name: HashMap::new(),
            registration_fee,
            renewal_fee,
            grace_period,
        }
    }

    /// Register a new name
    pub fn register(
        &mut self,
        request: RegistrationRequest,
        owner: Address,
        current_block: u64,
    ) -> Result<NameRecord> {
        // Validate name
        validate_name(&request.name)?;

        // Check if name is available
        if let Some(existing) = self.records.get(&request.name) {
            // Check if expired and past grace period
            if existing.expires_at > 0
                && current_block > existing.expires_at + self.grace_period
            {
                // Name can be re-registered
            } else {
                return Err(Error::NameTaken(request.name));
            }
        }

        // Calculate expiry
        let expires_at = if request.duration == 0 {
            0 // Permanent
        } else {
            current_block + request.duration
        };

        let record = NameRecord {
            name: request.name.clone(),
            owner,
            cid: request.cid,
            registered_at: current_block,
            expires_at,
            custom_domains: Vec::new(),
            metadata: request.metadata,
        };

        self.records.insert(request.name, record.clone());

        Ok(record)
    }

    /// Update CID for a name (must be owner)
    pub fn update(
        &mut self,
        name: &str,
        new_cid: ContentId,
        caller: Address,
        current_block: u64,
    ) -> Result<()> {
        let record = self.get_record_mut(name, current_block)?;

        if record.owner != caller {
            return Err(Error::NotOwner);
        }

        record.cid = new_cid;

        Ok(())
    }

    /// Transfer ownership of a name
    pub fn transfer(
        &mut self,
        name: &str,
        new_owner: Address,
        caller: Address,
        current_block: u64,
    ) -> Result<()> {
        let record = self.get_record_mut(name, current_block)?;

        if record.owner != caller {
            return Err(Error::NotOwner);
        }

        record.owner = new_owner;

        Ok(())
    }

    /// Renew a name registration
    pub fn renew(
        &mut self,
        name: &str,
        additional_duration: u64,
        caller: Address,
        current_block: u64,
    ) -> Result<u64> {
        let record = self.get_record_mut(name, current_block)?;

        if record.owner != caller {
            return Err(Error::NotOwner);
        }

        if record.expires_at == 0 {
            return Err(Error::InvalidOperation("Name is permanent".into()));
        }

        // Extend from current expiry or current block (if in grace period)
        let base = if record.expires_at > current_block {
            record.expires_at
        } else {
            current_block
        };

        record.expires_at = base + additional_duration;

        Ok(record.expires_at)
    }

    /// Resolve a name to its CID
    pub fn resolve(&self, name: &str, current_block: u64) -> Option<ContentId> {
        self.records.get(name).and_then(|record| {
            if self.is_valid(record, current_block) {
                Some(record.cid.clone())
            } else {
                None
            }
        })
    }

    /// Get full record for a name
    pub fn get_record(&self, name: &str, current_block: u64) -> Result<&NameRecord> {
        let record = self.records.get(name).ok_or_else(|| Error::NameNotFound(name.to_string()))?;

        if !self.is_valid(record, current_block) {
            return Err(Error::NameExpired(name.to_string()));
        }

        Ok(record)
    }

    /// Get mutable record
    fn get_record_mut(&mut self, name: &str, current_block: u64) -> Result<&mut NameRecord> {
        let record = self.records.get(name).ok_or_else(|| Error::NameNotFound(name.to_string()))?;

        // Check validity before returning mutable reference
        if !self.is_valid(record, current_block) {
            return Err(Error::NameExpired(name.to_string()));
        }

        Ok(self.records.get_mut(name).unwrap())
    }

    /// Check if a record is valid (not expired or within grace period)
    fn is_valid(&self, record: &NameRecord, current_block: u64) -> bool {
        if record.expires_at == 0 {
            return true; // Permanent
        }
        current_block <= record.expires_at + self.grace_period
    }

    /// Check if a name is available for registration
    pub fn is_available(&self, name: &str, current_block: u64) -> bool {
        match self.records.get(name) {
            None => validate_name(name).is_ok(),
            Some(record) => {
                record.expires_at > 0
                    && current_block > record.expires_at + self.grace_period
            }
        }
    }

    /// Add a custom domain to a name
    pub fn add_custom_domain(
        &mut self,
        name: &str,
        domain: String,
        caller: Address,
        current_block: u64,
    ) -> Result<()> {
        // Check domain isn't already linked
        if self.domain_to_name.contains_key(&domain) {
            return Err(Error::DomainAlreadyLinked(domain));
        }

        let record = self.get_record_mut(name, current_block)?;

        if record.owner != caller {
            return Err(Error::NotOwner);
        }

        record.custom_domains.push(domain.clone());
        self.domain_to_name.insert(domain, name.to_string());

        Ok(())
    }

    /// Remove a custom domain from a name
    pub fn remove_custom_domain(
        &mut self,
        name: &str,
        domain: &str,
        caller: Address,
        current_block: u64,
    ) -> Result<()> {
        let record = self.get_record_mut(name, current_block)?;

        if record.owner != caller {
            return Err(Error::NotOwner);
        }

        record.custom_domains.retain(|d| d != domain);
        self.domain_to_name.remove(domain);

        Ok(())
    }

    /// Resolve a custom domain to a name
    pub fn resolve_domain(&self, domain: &str) -> Option<&str> {
        self.domain_to_name.get(domain).map(|s| s.as_str())
    }

    /// Resolve a custom domain directly to CID
    pub fn resolve_domain_to_cid(&self, domain: &str, current_block: u64) -> Option<ContentId> {
        self.resolve_domain(domain)
            .and_then(|name| self.resolve(name, current_block))
    }

    /// Get registration fee
    pub fn registration_fee(&self) -> u64 {
        self.registration_fee
    }

    /// Get renewal fee
    pub fn renewal_fee(&self) -> u64 {
        self.renewal_fee
    }

    /// Get all names owned by an address
    pub fn names_by_owner(&self, owner: &Address, current_block: u64) -> Vec<&NameRecord> {
        self.records
            .values()
            .filter(|r| r.owner == *owner && self.is_valid(r, current_block))
            .collect()
    }
}

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

    fn make_cid(data: &[u8]) -> ContentId {
        ContentId::from_content(data)
    }

    #[test]
    fn test_register_and_resolve() {
        let mut registry = NameRegistry::new();
        let owner = [1u8; 32];
        let cid = make_cid(b"hello world");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 1000,
            metadata: None,
        };

        registry.register(request, owner, 0).unwrap();

        let resolved = registry.resolve("myapp", 0).unwrap();
        assert_eq!(resolved.digest, cid.digest);
    }

    #[test]
    fn test_update_cid() {
        let mut registry = NameRegistry::new();
        let owner = [1u8; 32];
        let cid1 = make_cid(b"version 1");
        let cid2 = make_cid(b"version 2");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid1.clone(),
            duration: 1000,
            metadata: None,
        };

        registry.register(request, owner, 0).unwrap();
        registry.update("myapp", cid2.clone(), owner, 0).unwrap();

        let resolved = registry.resolve("myapp", 0).unwrap();
        assert_eq!(resolved.digest, cid2.digest);
    }

    #[test]
    fn test_transfer_ownership() {
        let mut registry = NameRegistry::new();
        let owner1 = [1u8; 32];
        let owner2 = [2u8; 32];
        let cid = make_cid(b"hello");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 1000,
            metadata: None,
        };

        registry.register(request, owner1, 0).unwrap();
        registry.transfer("myapp", owner2, owner1, 0).unwrap();

        let record = registry.get_record("myapp", 0).unwrap();
        assert_eq!(record.owner, owner2);
    }

    #[test]
    fn test_name_expiry() {
        let mut registry = NameRegistry::new();
        let owner = [1u8; 32];
        let cid = make_cid(b"hello");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 100,
            metadata: None,
        };

        registry.register(request, owner, 0).unwrap();

        // Should resolve before expiry
        assert!(registry.resolve("myapp", 50).is_some());

        // Should still resolve during grace period
        assert!(registry.resolve("myapp", 150).is_some());

        // Should not resolve after grace period
        let after_grace = 100 + registry.grace_period + 1;
        assert!(registry.resolve("myapp", after_grace).is_none());
    }

    #[test]
    fn test_custom_domain() {
        let mut registry = NameRegistry::new();
        let owner = [1u8; 32];
        let cid = make_cid(b"hello");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 1000,
            metadata: None,
        };

        registry.register(request, owner, 0).unwrap();
        registry.add_custom_domain("myapp", "myapp.com".to_string(), owner, 0).unwrap();

        assert_eq!(registry.resolve_domain("myapp.com"), Some("myapp"));

        let resolved = registry.resolve_domain_to_cid("myapp.com", 0).unwrap();
        assert_eq!(resolved.digest, cid.digest);
    }

    #[test]
    fn test_not_owner_error() {
        let mut registry = NameRegistry::new();
        let owner = [1u8; 32];
        let other = [2u8; 32];
        let cid = make_cid(b"hello");

        let request = RegistrationRequest {
            name: "myapp".to_string(),
            cid: cid.clone(),
            duration: 1000,
            metadata: None,
        };

        registry.register(request, owner, 0).unwrap();

        // Other user cannot update
        let result = registry.update("myapp", make_cid(b"new"), other, 0);
        assert!(matches!(result, Err(Error::NotOwner)));
    }
}
