use crate::types::validation_errors::ValidationError;
use cpf_cnpj::cnpj::validate as validate_cnpj;
use cpf_cnpj::cpf::validate as validate_cpf;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum DocumentType {
    /// CPF (Cadastro de Pessoa Física)
    Cpf,
    /// CNPJ (Cadastro Nacional de Pessoa Jurídica)
    Cnpj,
    /// RG (Registro Geral)
    Rg,
    /// Passaporte
    Passport,
    /// Outro tipo de documento
    Other(String),
    /// CPF ou CNPJ
    #[serde(rename = "CPF_CNPJ")]
    CpfCnpj,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[repr(transparent)]
#[serde(try_from = "String")]
#[serde(into = "String")]
pub struct CnpjOrCpf(String);

impl TryFrom<String> for CnpjOrCpf {
    type Error = ValidationError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        // Valida com a formatação original
        if validate_cpf(&value) || validate_cnpj(&value) {
            // Remove toda a formatação (pontos, traços, barras) antes de armazenar
            let cleaned = value.chars().filter(|c| c.is_numeric()).collect::<String>();

            Ok(Self(cleaned))
        } else {
            Err(ValidationError::InvalidFormat {
                field: "cpf_or_cnpj".to_string(),
                message: format!("invalid cpf or cnpj {value}"),
            })
        }
    }
}

impl TryFrom<&str> for CnpjOrCpf {
    type Error = ValidationError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Self::try_from(value.to_string())
    }
}

impl From<CnpjOrCpf> for String {
    fn from(value: CnpjOrCpf) -> Self {
        value.0
    }
}

impl std::fmt::Display for CnpjOrCpf {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)?;
        Ok(())
    }
}

impl CnpjOrCpf {
    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn is_cpf(&self) -> bool {
        validate_cpf(&self.0)
    }

    pub fn is_cnpj(&self) -> bool {
        validate_cnpj(&self.0)
    }
}

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

    #[tokio::test]
    async fn should_create_cnpj_or_cpf_type_with_valid_cpf() {
        let cnpj_or_cpf = CnpjOrCpf::try_from("40743854063");
        assert!(cnpj_or_cpf.is_ok());
    }

    #[tokio::test]
    async fn should_create_cnpj_or_cpf_type_with_valid_cpf_string() {
        let cnpj_or_cpf = CnpjOrCpf::try_from("40743854063".to_string());
        assert!(cnpj_or_cpf.is_ok());
    }

    #[tokio::test]
    async fn should_create_cnpj_or_cpf_type_with_valid_cnpj() {
        // CNPJ válido: 11.222.333/0001-81
        let cnpj_or_cpf = CnpjOrCpf::try_from("11222333000181");
        assert!(cnpj_or_cpf.is_ok());
    }

    #[tokio::test]
    async fn should_create_cnpj_or_cpf_type_with_valid_cnpj_string() {
        // CNPJ válido: 11.222.333/0001-81
        let cnpj_or_cpf = CnpjOrCpf::try_from("11222333000181".to_string());
        assert!(cnpj_or_cpf.is_ok());
    }

    #[tokio::test]
    async fn should_fail_with_invalid_cpf_and_cnpj() {
        let cnpj_or_cpf = CnpjOrCpf::try_from("40743854012".to_string());
        assert!(cnpj_or_cpf.is_err());
        assert_eq!(
            cnpj_or_cpf.err(),
            Some(ValidationError::InvalidFormat {
                field: "cpf_or_cnpj".to_string(),
                message: "invalid cpf or cnpj 40743854012".to_string(),
            })
        );
    }

    #[tokio::test]
    async fn should_fail_with_empty_string() {
        let cnpj_or_cpf = CnpjOrCpf::try_from("".to_string());
        assert!(cnpj_or_cpf.is_err());
    }

    #[test]
    fn test_tax_id_cpf() {
        // CPF válido com formatação: 407.438.540-63
        let tax_id_result = CnpjOrCpf::try_from("407.438.540-63");
        match tax_id_result {
            Ok(tax_id) => {
                assert_eq!(tax_id.as_str(), "40743854063");
                assert!(tax_id.is_cpf());
                assert!(!tax_id.is_cnpj());
            }
            Err(err) => {
                unreachable!("expected success tax_id, but receive error: {:?}", err);
            }
        }
    }

    #[test]
    fn test_tax_id_cnpj() {
        let tax_id_result = CnpjOrCpf::try_from("36.002.518/0001-01");
        match tax_id_result {
            Ok(tax_id) => {
                assert_eq!(tax_id.as_str(), "36002518000101");
                assert!(!tax_id.is_cpf());
                assert!(tax_id.is_cnpj());
            }
            Err(err) => {
                unreachable!("expected success tax_id, but receive error: {:?}", err);
            }
        }
    }
}
