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

/// RDAP response for domain, IP, or ASN lookups.
/// Follows RFC 7483 (JSON Responses for RDAP).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RdapResponse {
    #[serde(default)]
    pub object_class_name: Option<String>,

    #[serde(default)]
    pub handle: Option<String>,

    #[serde(default)]
    pub ldh_name: Option<String>,

    #[serde(default)]
    pub unicode_name: Option<String>,

    #[serde(default)]
    pub status: Vec<String>,

    #[serde(default)]
    pub events: Vec<RdapEvent>,

    #[serde(default)]
    pub entities: Vec<RdapEntity>,

    #[serde(default)]
    pub nameservers: Vec<RdapNameserver>,

    #[serde(default)]
    pub secure_dns: Option<SecureDns>,

    #[serde(default)]
    pub links: Vec<RdapLink>,

    #[serde(default)]
    pub remarks: Vec<RdapRemark>,

    #[serde(default)]
    pub notices: Vec<RdapNotice>,

    #[serde(default)]
    pub port43: Option<String>,

    // IP-specific fields
    #[serde(default)]
    pub start_address: Option<String>,

    #[serde(default)]
    pub end_address: Option<String>,

    #[serde(default)]
    pub ip_version: Option<String>,

    #[serde(default)]
    pub name: Option<String>,

    #[serde(default)]
    #[serde(rename = "type")]
    pub network_type: Option<String>,

    #[serde(default)]
    pub country: Option<String>,

    #[serde(default)]
    pub parent_handle: Option<String>,

    // ASN-specific fields
    #[serde(default)]
    pub start_autnum: Option<u32>,

    #[serde(default)]
    pub end_autnum: Option<u32>,

    // Raw JSON for extended data
    #[serde(flatten)]
    pub extra: serde_json::Map<String, serde_json::Value>,
}

/// An event in the lifecycle of an RDAP object (registration, expiration, etc.).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RdapEvent {
    pub event_action: String,
    #[serde(default)]
    pub event_date: Option<String>,
    #[serde(default)]
    pub event_actor: Option<String>,
}

impl RdapEvent {
    pub fn parsed_date(&self) -> Option<DateTime<Utc>> {
        self.event_date.as_ref()?.parse().ok()
    }
}

/// An entity associated with an RDAP object (registrar, registrant, admin, tech contact).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RdapEntity {
    #[serde(default)]
    pub object_class_name: Option<String>,

    #[serde(default)]
    pub handle: Option<String>,

    #[serde(default)]
    pub roles: Vec<String>,

    #[serde(default)]
    pub public_ids: Vec<PublicId>,

    #[serde(default)]
    pub vcard_array: Option<serde_json::Value>,

    #[serde(default)]
    pub entities: Vec<RdapEntity>,

    #[serde(default)]
    pub remarks: Vec<RdapRemark>,

    #[serde(default)]
    pub links: Vec<RdapLink>,

    #[serde(default)]
    pub events: Vec<RdapEvent>,

    #[serde(default)]
    pub status: Vec<String>,
}

impl RdapEntity {
    pub fn get_name(&self) -> Option<String> {
        if let Some(vcard) = &self.vcard_array {
            if let Some(arr) = vcard.as_array() {
                if arr.len() > 1 {
                    if let Some(props) = arr[1].as_array() {
                        for prop in props {
                            if let Some(prop_arr) = prop.as_array() {
                                if prop_arr.len() >= 4
                                    && prop_arr[0].as_str() == Some("fn") {
                                    return prop_arr[3].as_str().map(String::from);
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }

    pub fn get_organization(&self) -> Option<String> {
        if let Some(vcard) = &self.vcard_array {
            if let Some(arr) = vcard.as_array() {
                if arr.len() > 1 {
                    if let Some(props) = arr[1].as_array() {
                        for prop in props {
                            if let Some(prop_arr) = prop.as_array() {
                                if prop_arr.len() >= 4
                                    && prop_arr[0].as_str() == Some("org") {
                                    // org value can be a string or array
                                    if let Some(org_str) = prop_arr[3].as_str() {
                                        return Some(org_str.to_string());
                                    } else if let Some(org_arr) = prop_arr[3].as_array() {
                                        // org is often ["Company Name", "Department"]
                                        if let Some(first) = org_arr.first() {
                                            if let Some(org_str) = first.as_str() {
                                                return Some(org_str.to_string());
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }

    pub fn get_email(&self) -> Option<String> {
        if let Some(vcard) = &self.vcard_array {
            if let Some(arr) = vcard.as_array() {
                if arr.len() > 1 {
                    if let Some(props) = arr[1].as_array() {
                        for prop in props {
                            if let Some(prop_arr) = prop.as_array() {
                                if prop_arr.len() >= 4
                                    && prop_arr[0].as_str() == Some("email") {
                                    return prop_arr[3].as_str().map(String::from);
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }

    pub fn get_phone(&self) -> Option<String> {
        if let Some(vcard) = &self.vcard_array {
            if let Some(arr) = vcard.as_array() {
                if arr.len() > 1 {
                    if let Some(props) = arr[1].as_array() {
                        for prop in props {
                            if let Some(prop_arr) = prop.as_array() {
                                if prop_arr.len() >= 4
                                    && prop_arr[0].as_str() == Some("tel") {
                                    if let Some(phone) = prop_arr[3].as_str() {
                                        return Some(phone.to_string());
                                    } else if let Some(phone_obj) = prop_arr[3].as_object() {
                                        // Sometimes phone is {"uri": "tel:+1234567890"}
                                        if let Some(uri) = phone_obj.get("uri") {
                                            if let Some(uri_str) = uri.as_str() {
                                                return Some(uri_str.trim_start_matches("tel:").to_string());
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }

    pub fn get_address(&self) -> Option<String> {
        if let Some(vcard) = &self.vcard_array {
            if let Some(arr) = vcard.as_array() {
                if arr.len() > 1 {
                    if let Some(props) = arr[1].as_array() {
                        for prop in props {
                            if let Some(prop_arr) = prop.as_array() {
                                if prop_arr.len() >= 4
                                    && prop_arr[0].as_str() == Some("adr") {
                                    // adr is usually an array: [pobox, ext, street, city, state, postal, country]
                                    if let Some(adr_arr) = prop_arr[3].as_array() {
                                        let parts: Vec<String> = adr_arr
                                            .iter()
                                            .filter_map(|v| v.as_str())
                                            .filter(|s| !s.is_empty())
                                            .map(String::from)
                                            .collect();
                                        if !parts.is_empty() {
                                            return Some(parts.join(", "));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }

    pub fn get_country(&self) -> Option<String> {
        if let Some(vcard) = &self.vcard_array {
            if let Some(arr) = vcard.as_array() {
                if arr.len() > 1 {
                    if let Some(props) = arr[1].as_array() {
                        for prop in props {
                            if let Some(prop_arr) = prop.as_array() {
                                // Check for country in adr field (index 6)
                                if prop_arr.len() >= 4
                                    && prop_arr[0].as_str() == Some("adr") {
                                    if let Some(adr_arr) = prop_arr[3].as_array() {
                                        if let Some(country) = adr_arr.get(6) {
                                            if let Some(country_str) = country.as_str() {
                                                if !country_str.is_empty() {
                                                    return Some(country_str.to_string());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        None
    }
}

/// A public identifier for an RDAP entity (e.g., IANA Registrar ID).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicId {
    #[serde(rename = "type")]
    pub id_type: String,
    pub identifier: String,
}

/// A nameserver associated with a domain in RDAP.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RdapNameserver {
    #[serde(default)]
    pub object_class_name: Option<String>,

    #[serde(default)]
    pub ldh_name: Option<String>,

    #[serde(default)]
    pub unicode_name: Option<String>,

    #[serde(default)]
    pub ip_addresses: Option<IpAddresses>,

    #[serde(default)]
    pub status: Vec<String>,

    #[serde(default)]
    pub links: Vec<RdapLink>,
}

/// IPv4 and IPv6 addresses associated with a nameserver.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpAddresses {
    #[serde(default)]
    pub v4: Vec<String>,
    #[serde(default)]
    pub v6: Vec<String>,
}

/// DNSSEC information for a domain.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecureDns {
    #[serde(default)]
    pub delegation_signed: Option<bool>,
    #[serde(default)]
    pub ds_data: Vec<DsData>,
    #[serde(default)]
    pub key_data: Vec<KeyData>,
}

/// DNSSEC DS (Delegation Signer) record data.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DsData {
    pub key_tag: u16,
    pub algorithm: u8,
    pub digest_type: u8,
    pub digest: String,
}

/// DNSSEC key data.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct KeyData {
    pub flags: u16,
    pub protocol: u8,
    pub algorithm: u8,
    pub public_key: String,
}

/// A link to related RDAP resources.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RdapLink {
    #[serde(default)]
    pub value: Option<String>,
    #[serde(default)]
    pub rel: Option<String>,
    #[serde(default)]
    pub href: Option<String>,
    #[serde(default)]
    #[serde(rename = "type")]
    pub media_type: Option<String>,
}

/// A remark or note attached to an RDAP object.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RdapRemark {
    #[serde(default)]
    pub title: Option<String>,
    #[serde(default)]
    pub description: Vec<String>,
    #[serde(default)]
    pub links: Vec<RdapLink>,
}

/// A notice from the RDAP server (terms of service, rate limiting, etc.).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RdapNotice {
    #[serde(default)]
    pub title: Option<String>,
    #[serde(default)]
    pub description: Vec<String>,
    #[serde(default)]
    pub links: Vec<RdapLink>,
}

impl RdapResponse {
    pub fn domain_name(&self) -> Option<&str> {
        self.ldh_name.as_deref().or(self.unicode_name.as_deref())
    }

    pub fn get_registrar(&self) -> Option<String> {
        for entity in &self.entities {
            if entity.roles.iter().any(|r| r == "registrar") {
                return entity.get_name().or_else(|| entity.handle.clone());
            }
        }
        None
    }

    pub fn get_registrant(&self) -> Option<String> {
        for entity in &self.entities {
            if entity.roles.iter().any(|r| r == "registrant") {
                return entity.get_name().or_else(|| entity.handle.clone());
            }
        }
        None
    }

    pub fn get_registrant_organization(&self) -> Option<String> {
        for entity in &self.entities {
            if entity.roles.iter().any(|r| r == "registrant") {
                if let Some(org) = entity.get_organization() {
                    // Filter out redacted values
                    let org_lower = org.to_lowercase();
                    if !org_lower.contains("redacted") && !org.is_empty() {
                        return Some(org);
                    }
                }
            }
        }
        None
    }

    pub fn creation_date(&self) -> Option<DateTime<Utc>> {
        self.events
            .iter()
            .find(|e| e.event_action == "registration")
            .and_then(|e| e.parsed_date())
    }

    pub fn expiration_date(&self) -> Option<DateTime<Utc>> {
        self.events
            .iter()
            .find(|e| e.event_action == "expiration")
            .and_then(|e| e.parsed_date())
    }

    pub fn last_updated(&self) -> Option<DateTime<Utc>> {
        self.events
            .iter()
            .find(|e| e.event_action == "last changed" || e.event_action == "last update of RDAP database")
            .and_then(|e| e.parsed_date())
    }

    pub fn nameserver_names(&self) -> Vec<String> {
        self.nameservers
            .iter()
            .filter_map(|ns| ns.ldh_name.clone().or_else(|| ns.unicode_name.clone()))
            .collect()
    }

    pub fn is_dnssec_signed(&self) -> bool {
        self.secure_dns
            .as_ref()
            .map(|s| s.delegation_signed.unwrap_or(false))
            .unwrap_or(false)
    }

    /// Returns an entity by its role.
    pub fn get_entity_by_role(&self, role: &str) -> Option<&RdapEntity> {
        self.entities
            .iter()
            .find(|e| e.roles.iter().any(|r| r == role))
    }

    /// Returns all contact information for a specific role.
    pub fn get_contact_info(&self, role: &str) -> Option<ContactInfo> {
        let entity = self.get_entity_by_role(role)?;
        Some(ContactInfo {
            name: entity.get_name(),
            organization: entity.get_organization(),
            email: entity.get_email(),
            phone: entity.get_phone(),
            address: entity.get_address(),
            country: entity.get_country(),
        })
    }

    pub fn get_admin_contact(&self) -> Option<ContactInfo> {
        self.get_contact_info("administrative")
    }

    pub fn get_tech_contact(&self) -> Option<ContactInfo> {
        self.get_contact_info("technical")
    }

    pub fn get_billing_contact(&self) -> Option<ContactInfo> {
        self.get_contact_info("billing")
    }

    pub fn get_registrant_contact(&self) -> Option<ContactInfo> {
        self.get_contact_info("registrant")
    }
}

/// Contact information extracted from RDAP entity.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContactInfo {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub organization: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub country: Option<String>,
}

impl ContactInfo {
    /// Checks if the contact has any non-redacted information.
    pub fn has_info(&self) -> bool {
        let has_data = self.name.is_some()
            || self.organization.is_some()
            || self.email.is_some()
            || self.phone.is_some()
            || self.address.is_some()
            || self.country.is_some();

        if !has_data {
            return false;
        }

        // Filter out redacted values
        let is_redacted = |s: &Option<String>| {
            s.as_ref().is_some_and(|v| {
                let lower = v.to_lowercase();
                lower.contains("redacted") || lower.contains("data protected") || v.is_empty()
            })
        };

        !is_redacted(&self.name)
            || !is_redacted(&self.organization)
            || !is_redacted(&self.email)
            || !is_redacted(&self.phone)
            || !is_redacted(&self.address)
            || !is_redacted(&self.country)
    }
}
