use crate::customer_email::CustomerEmail;
use crate::customer_id::CustomerId;
use crate::customer_name::CustomerName;
use crate::risk::{RiskLevel, RiskScore};
use crate::tax_id::{CnpjOrCpf, DocumentType};
use crate::types::shared::date_iso_8601::DateIso8601;
use crate::types::shared::phone_number::PhoneNumber;
use crate::types::validation_errors::ValidationError;
use serde::{Deserialize, Serialize};

pub mod customer_email;
pub mod customer_id;
pub mod customer_name;
pub mod risk;
pub mod tax_id;

/// Dados do cliente
///
/// Esta estrutura utiliza tipos seguros que garantem validação em tempo de compilação
/// ou falha rápida na criação/parsing.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Customer {
    /// ID único do cliente no sistema do lojista (obrigatório)
    pub id: CustomerId,

    /// Nome completo do cliente (obrigatório pela API)
    pub name: CustomerName,

    /// Email do cliente (obrigatório pela API)
    pub email: CustomerEmail,

    /// Telefone do cliente (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone1: Option<PhoneNumber>,

    /// Telefone alternativo (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone2: Option<PhoneNumber>,

    /// CPF ou CNPJ do cliente (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tax_id: Option<CnpjOrCpf>,

    /// Tipo do documento fiscal (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub document_type: Option<DocumentType>,

    /// Data de nascimento no formato ISO 8601(YYYY-MM-DD) (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dob: Option<DateIso8601>,

    /// Data de criação da conta no formato ISO 8601(YYYY-MM-DD)  (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<DateIso8601>,

    /// Indica se o cliente é novo (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub new: Option<bool>,

    /// Indica se o cliente é VIP (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vip: Option<bool>,

    /// Tipo do cliente (recomendado)
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub customer_type: Option<String>,

    /// Nível de risco (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub risk_level: Option<RiskLevel>,

    /// Score de risco (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub risk_score: Option<RiskScore>,

    /// Nome completo da mãe (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mother_name: Option<CustomerName>,
}

impl Customer {
    /// Cria um builder para construir um cliente
    pub fn builder() -> CustomerBuilder {
        CustomerBuilder::default()
    }
}

#[derive(Default)]
pub struct CustomerBuilder {
    id: Option<CustomerId>,
    name: Option<CustomerName>,
    email: Option<CustomerEmail>,
    phone1: Option<PhoneNumber>,
    phone2: Option<PhoneNumber>,
    tax_id: Option<CnpjOrCpf>,
    document_type: Option<DocumentType>,
    dob: Option<DateIso8601>,
    created_at: Option<DateIso8601>,
    new: Option<bool>,
    vip: Option<bool>,
    customer_type: Option<String>,
    risk_level: Option<RiskLevel>,
    risk_score: Option<RiskScore>,
    mother_name: Option<CustomerName>,
}

impl CustomerBuilder {
    /// Define o ID do cliente (obrigatório)
    pub fn id(mut self, id: impl Into<String>) -> Result<Self, ValidationError> {
        self.id = Some(CustomerId::try_new(id)?);
        Ok(self)
    }

    /// Define o nome do cliente
    pub fn name(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
        self.name = Some(CustomerName::try_new(name)?);
        Ok(self)
    }

    /// Define o email do cliente
    pub fn email(mut self, email: impl Into<String>) -> Result<Self, ValidationError> {
        self.email = Some(CustomerEmail::new(email)?);
        Ok(self)
    }

    /// Define o telefone principal
    pub fn phone1(mut self, phone: impl Into<String>) -> Result<Self, ValidationError> {
        self.phone1 = Some(PhoneNumber::new(phone)?);
        Ok(self)
    }

    /// Define o telefone alternativo
    pub fn phone2(mut self, phone: impl Into<String>) -> Result<Self, ValidationError> {
        self.phone2 = Some(PhoneNumber::new(phone)?);
        Ok(self)
    }

    /// Define o CPF/CNPJ (com limpeza automática de pontuação)
    pub fn tax_id(mut self, tax_id: impl Into<String>) -> Result<Self, ValidationError> {
        self.tax_id = Some(CnpjOrCpf::try_from(tax_id.into())?);
        Ok(self)
    }

    /// Define o tipo de documento
    pub fn document_type(mut self, doc_type: DocumentType) -> Self {
        self.document_type = Some(doc_type);
        self
    }

    /// Define a data de nascimento (formato: AAAA-MM-DD)
    pub fn dob(mut self, dob: impl Into<String>) -> Result<Self, ValidationError> {
        self.dob = Some(DateIso8601::new(dob)?);
        Ok(self)
    }

    /// Define a data de criação da conta (formato: AAAA-MM-DD)
    pub fn created_at(mut self, date: impl Into<String>) -> Result<Self, ValidationError> {
        self.created_at = Some(DateIso8601::new(date)?);
        Ok(self)
    }

    /// Define se é novo cliente
    pub fn new(mut self, is_new: bool) -> Self {
        self.new = Some(is_new);
        self
    }

    /// Define se é VIP
    pub fn vip(mut self, is_vip: bool) -> Self {
        self.vip = Some(is_vip);
        self
    }

    /// Define o tipo do cliente
    pub fn customer_type(mut self, customer_type: impl Into<String>) -> Self {
        self.customer_type = Some(customer_type.into());
        self
    }

    /// Define o nível de risco
    pub fn risk_level(mut self, level: RiskLevel) -> Self {
        self.risk_level = Some(level);
        self
    }

    /// Define o score de risco (0.0 a 1.0)
    pub fn risk_score(mut self, score: f64) -> Result<Self, ValidationError> {
        self.risk_score = Some(RiskScore::new(score)?);
        Ok(self)
    }

    /// Define o nome da mãe
    pub fn mother_name(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
        self.mother_name = Some(CustomerName::try_new(name)?);
        Ok(self)
    }

    /// Constrói o cliente
    ///
    /// # Erros
    ///
    /// Retorna erro se o ID obrigatório não foi fornecido
    pub fn build(self) -> Result<Customer, ValidationError> {
        let id = self
            .id
            .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;

        let name = self
            .name
            .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;

        let email = self
            .email
            .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;

        Ok(Customer {
            id,
            name,
            email,
            phone1: self.phone1,
            phone2: self.phone2,
            tax_id: self.tax_id,
            document_type: self.document_type,
            dob: self.dob,
            created_at: self.created_at,
            new: self.new,
            vip: self.vip,
            customer_type: self.customer_type,
            risk_level: self.risk_level,
            risk_score: self.risk_score,
            mother_name: self.mother_name,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::customer_id::CustomerId;
    use crate::types::validation_errors::ValidationError;

    #[test]
    fn test_customer_id_valid() {
        let id = CustomerId::try_new("CUSTOMER123").unwrap();
        assert_eq!(id.as_str(), "CUSTOMER123");
    }

    #[test]
    fn test_customer_id_empty() {
        let result = CustomerId::try_new("");
        assert!(result.is_err());
    }

    #[test]
    fn test_customer_id_too_long() {
        let long_id = "A".repeat(101);
        let result = CustomerId::try_new(long_id);
        assert!(result.is_err());
    }

    #[test]
    fn test_customer_name_valid() {
        let name = CustomerName::try_new("João Silva").unwrap();
        assert_eq!(name.as_str(), "João Silva");
    }

    #[test]
    fn test_customer_name_trim() {
        let name = CustomerName::try_new("  João Silva  ").unwrap();
        assert_eq!(name.as_str(), "João Silva");
    }

    #[test]
    fn test_email_valid() {
        let email = CustomerEmail::new("joao@example.com").unwrap();
        assert_eq!(email.as_str(), "joao@example.com");
    }

    #[test]
    fn test_email_lowercase() {
        let email = CustomerEmail::new("JOAO@EXAMPLE.COM").unwrap();
        assert_eq!(email.as_str(), "joao@example.com");
    }

    #[test]
    fn test_email_invalid() {
        assert!(CustomerEmail::new("invalid").is_err());
        assert!(CustomerEmail::new("invalid@").is_err());
        assert!(CustomerEmail::new("@invalid").is_err());
    }

    #[test]
    fn test_date_iso8601_valid() {
        let date = DateIso8601::new("2025-11-15").unwrap();
        assert_eq!(date.as_str(), "2025-11-15");
    }

    #[test]
    fn test_date_iso8601_invalid() {
        assert!(DateIso8601::new("2025/11/15").is_err());
        assert!(DateIso8601::new("15-11-2025").is_err());
        assert!(DateIso8601::new("2025-11").is_err());
    }

    #[test]
    fn test_risk_score_valid() {
        let score = RiskScore::new(0.5).unwrap();
        assert_eq!(score.value(), 0.5);
    }

    #[test]
    fn test_risk_score_out_of_range() {
        assert!(RiskScore::new(-0.1).is_err());
        assert!(RiskScore::new(1.1).is_err());
    }

    #[test]
    fn test_customer_builder_complete() -> Result<(), ValidationError> {
        let customer = Customer::builder()
            .id("CUSTOMER123")?
            .name("João Silva")?
            .email("joao@example.com")?
            .phone1("+55 11 98765-4321")?
            .tax_id("80.232.738/0001-13")?
            .document_type(DocumentType::Cnpj)
            .dob("1990-01-15")?
            .created_at("2020-05-10")?
            .new(false)
            .vip(true)
            .customer_type("premium")
            .risk_level(RiskLevel::Low)
            .risk_score(0.2)?
            .mother_name("Maria Silva")?
            .build()?;

        assert_eq!(customer.id.as_str(), "CUSTOMER123");
        assert_eq!(customer.name.as_str(), "João Silva");
        assert_eq!(customer.email.as_str(), "joao@example.com");
        assert!(customer.vip.unwrap());

        Ok(())
    }

    #[test]
    fn test_customer_builder_minimal() -> Result<(), ValidationError> {
        let customer = Customer::builder()
            .id("CUSTOMER456")?
            .name("some name")?
            .email("some@email.com")?
            .build()?;

        assert_eq!(customer.id.as_str(), "CUSTOMER456");
        assert_eq!(customer.name.as_str(), "some name");
        assert_eq!(customer.email.as_str(), "some@email.com");

        Ok(())
    }

    #[test]
    fn test_customer_builder_no_id() {
        let result = Customer::builder().build();
        assert!(result.is_err());
    }

    #[test]
    fn test_serde_serialization() -> Result<(), Box<dyn std::error::Error>> {
        let customer = Customer::builder()
            .id("TEST123")?
            .name("Test User")?
            .email("test@example.com")?
            .tax_id("47568576043")?
            .build()?;

        let json = serde_json::to_string(&customer)?;
        assert!(json.contains("TEST123"));
        assert!(json.contains("test@example.com"));

        Ok(())
    }

    #[test]
    fn test_serde_deserialization() -> Result<(), Box<dyn std::error::Error>> {
        let json = r#"{
            "id": "CUSTOMER789",
            "name": "Maria Santos",
            "email": "maria@example.com",
            "tax_id": "98765432100"
        }"#;

        let customer: Customer = serde_json::from_str(json)?;
        assert_eq!(customer.id.as_str(), "CUSTOMER789");
        assert_eq!(customer.name.as_str(), "Maria Santos");

        Ok(())
    }

    #[test]
    fn test_document_type_serialization() -> Result<(), Box<dyn std::error::Error>> {
        assert_eq!(serde_json::to_string(&DocumentType::Cpf)?, "\"CPF\"");
        assert_eq!(serde_json::to_string(&DocumentType::Cnpj)?, "\"CNPJ\"");
        assert_eq!(
            serde_json::to_string(&DocumentType::CpfCnpj)?,
            "\"CPF_CNPJ\""
        );

        Ok(())
    }

    #[test]
    fn test_risk_level_serialization() -> Result<(), Box<dyn std::error::Error>> {
        assert_eq!(serde_json::to_string(&RiskLevel::Low)?, "\"low\"");
        assert_eq!(serde_json::to_string(&RiskLevel::Medium)?, "\"medium\"");
        assert_eq!(serde_json::to_string(&RiskLevel::High)?, "\"high\"");

        Ok(())
    }
}
