use super::{LoyaltyProgram, PassengerName};
use crate::types::address::Country;
use crate::types::shared::date_iso_8601::DateIso8601;
use crate::types::validation_errors::ValidationError;
use crate::{TravelDocumentNumber, TravelDocumentType};
use serde::{Deserialize, Serialize};

/// Dados do passageiro
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Passenger {
    /// Nome completo do passageiro
    pub name: PassengerName,

    /// Número do documento
    pub document: TravelDocumentNumber,

    /// Tipo do documento
    pub document_type: TravelDocumentType,

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

    /// País de nascimento (ISO 3166-2)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nationality: Option<Country>,

    /// Flag de viajante frequente
    #[serde(skip_serializing_if = "Option::is_none")]
    pub frequent_traveler: Option<bool>,

    /// Flag de necessidades especiais
    #[serde(skip_serializing_if = "Option::is_none")]
    pub special_needs: Option<bool>,

    /// Programa de fidelidade
    #[serde(skip_serializing_if = "Option::is_none")]
    pub loyalty: Option<LoyaltyProgram>,
}

impl Passenger {
    /// Cria um builder para construir um passageiro
    pub fn builder() -> PassengerBuilder {
        PassengerBuilder::default()
    }
}

/// Builder para criar passageiros
#[derive(Default)]
pub struct PassengerBuilder {
    name: Option<PassengerName>,
    document: Option<TravelDocumentNumber>,
    document_type: Option<TravelDocumentType>,
    dob: Option<DateIso8601>,
    nationality: Option<Country>,
    frequent_traveler: Option<bool>,
    special_needs: Option<bool>,
    loyalty: Option<LoyaltyProgram>,
}

impl PassengerBuilder {
    pub fn name(mut self, name: PassengerName) -> Self {
        self.name = Some(name);
        self
    }

    pub fn name_str(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
        self.name = Some(PassengerName::try_new(name)?);
        Ok(self)
    }

    pub fn document(mut self, document: TravelDocumentNumber) -> Self {
        self.document = Some(document);
        self
    }

    pub fn document_str(mut self, document: impl Into<String>) -> Result<Self, ValidationError> {
        self.document = Some(TravelDocumentNumber::try_new(document)?);
        Ok(self)
    }

    pub fn document_type(mut self, doc_type: TravelDocumentType) -> Self {
        self.document_type = Some(doc_type);
        self
    }

    pub fn dob(mut self, dob: DateIso8601) -> Self {
        self.dob = Some(dob);
        self
    }

    pub fn dob_str(mut self, dob: impl Into<String>) -> Result<Self, ValidationError> {
        self.dob = Some(DateIso8601::new(dob)?);
        Ok(self)
    }

    pub fn nationality(mut self, nationality: Country) -> Self {
        self.nationality = Some(nationality);
        self
    }

    pub fn nationality_code(mut self, code: impl Into<String>) -> Result<Self, ValidationError> {
        self.nationality = Some(Country::from_code(code)?);
        Ok(self)
    }

    pub fn frequent_traveler(mut self, frequent: bool) -> Self {
        self.frequent_traveler = Some(frequent);
        self
    }

    pub fn special_needs(mut self, needs: bool) -> Self {
        self.special_needs = Some(needs);
        self
    }

    pub fn loyalty(mut self, loyalty: LoyaltyProgram) -> Self {
        self.loyalty = Some(loyalty);
        self
    }

    pub fn build(self) -> Result<Passenger, ValidationError> {
        Ok(Passenger {
            name: self
                .name
                .ok_or_else(|| ValidationError::EmptyField("passenger_name".to_string()))?,
            document: self
                .document
                .ok_or_else(|| ValidationError::EmptyField("passenger_document".to_string()))?,
            document_type: self
                .document_type
                .ok_or_else(|| ValidationError::EmptyField("document_type".to_string()))?,
            dob: self.dob,
            nationality: self.nationality,
            frequent_traveler: self.frequent_traveler,
            special_needs: self.special_needs,
            loyalty: self.loyalty,
        })
    }
}

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

    #[test]
    fn test_passenger_builder() {
        let passenger = Passenger::builder()
            .name_str("Júlia da Silva")
            .unwrap()
            .document_str("12345678909")
            .unwrap()
            .document_type(TravelDocumentType::Id)
            .dob_str("1970-01-01")
            .unwrap()
            .nationality(Country::BR)
            .frequent_traveler(true)
            .special_needs(false)
            .loyalty(LoyaltyProgram::new("smiles", "gold"))
            .build()
            .unwrap();

        assert_eq!(passenger.name.as_str(), "Júlia da Silva");
        assert_eq!(passenger.document.as_str(), "12345678909");
        assert_eq!(passenger.document_type, TravelDocumentType::Id);
        assert_eq!(passenger.frequent_traveler, Some(true));
    }

    #[test]
    fn test_passenger_minimal() {
        let passenger = Passenger::builder()
            .name_str("João")
            .unwrap()
            .document_str("ABC123")
            .unwrap()
            .document_type(TravelDocumentType::Passport)
            .build()
            .unwrap();

        assert_eq!(passenger.name.as_str(), "João");
        assert!(passenger.dob.is_none());
        assert!(passenger.loyalty.is_none());
    }

    #[test]
    fn test_passenger_missing_required_field() {
        let result = Passenger::builder().name_str("João").unwrap().build();

        assert!(result.is_err());
    }

    #[test]
    fn test_passenger_serialization() {
        let passenger = Passenger::builder()
            .name_str("Carlos")
            .unwrap()
            .document_str("XYZ1234")
            .unwrap()
            .document_type(TravelDocumentType::Passport)
            .nationality(Country::US)
            .build()
            .unwrap();

        let json = serde_json::to_string(&passenger).unwrap();
        assert!(json.contains("Carlos"));
        assert!(json.contains("XYZ1234"));
    }
}

