use crate::types::validation_errors::ValidationError;
use serde::{Deserialize, Serialize};

use super::{AirportCode, CityName, CompanyName, FareBasis, TravelClass};

/// Informações de uma perna da viagem (ida ou volta)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TravelLeg {
    /// Cidade de origem (obrigatório se type=bus)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin_city: Option<CityName>,

    /// Cidade de destino (obrigatório se type=bus)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub destination_city: Option<CityName>,

    /// Aeroporto de origem (obrigatório se type=flight)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin_airport: Option<AirportCode>,

    /// Aeroporto de destino (obrigatório se type=flight)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub destination_airport: Option<AirportCode>,

    /// Data e hora do embarque em UTC (formato ISO 8601: YYYY-MM-DDThh:mmZ)
    /// Max 17 chars
    pub date: String,

    /// Número de conexões
    #[serde(skip_serializing_if = "Option::is_none")]
    pub number_of_connections: Option<u32>,

    /// Classe da viagem
    #[serde(skip_serializing_if = "Option::is_none")]
    pub class: Option<TravelClass>,

    /// Código da classe (fare basis)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fare_basis: Option<FareBasis>,

    /// Nome da companhia
    #[serde(skip_serializing_if = "Option::is_none")]
    pub company: Option<CompanyName>,
}

impl TravelLeg {
    /// Cria um builder para construir uma perna de viagem
    pub fn builder() -> TravelLegBuilder {
        TravelLegBuilder::default()
    }
}

/// Builder para criar pernas de viagem
#[derive(Default)]
pub struct TravelLegBuilder {
    origin_city: Option<CityName>,
    destination_city: Option<CityName>,
    origin_airport: Option<AirportCode>,
    destination_airport: Option<AirportCode>,
    date: Option<String>,
    number_of_connections: Option<u32>,
    class: Option<TravelClass>,
    fare_basis: Option<FareBasis>,
    company: Option<CompanyName>,
}

impl TravelLegBuilder {
    pub fn origin_city(mut self, city: CityName) -> Self {
        self.origin_city = Some(city);
        self
    }

    pub fn origin_city_str(mut self, city: impl Into<String>) -> Result<Self, ValidationError> {
        self.origin_city = Some(CityName::try_new(city)?);
        Ok(self)
    }

    pub fn destination_city(mut self, city: CityName) -> Self {
        self.destination_city = Some(city);
        self
    }

    pub fn destination_city_str(
        mut self,
        city: impl Into<String>,
    ) -> Result<Self, ValidationError> {
        self.destination_city = Some(CityName::try_new(city)?);
        Ok(self)
    }

    pub fn origin_airport(mut self, airport: AirportCode) -> Self {
        self.origin_airport = Some(airport);
        self
    }

    pub fn origin_airport_str(
        mut self,
        airport: impl Into<String>,
    ) -> Result<Self, ValidationError> {
        self.origin_airport = Some(AirportCode::try_new(airport)?);
        Ok(self)
    }

    pub fn destination_airport(mut self, airport: AirportCode) -> Self {
        self.destination_airport = Some(airport);
        self
    }

    pub fn destination_airport_str(
        mut self,
        airport: impl Into<String>,
    ) -> Result<Self, ValidationError> {
        self.destination_airport = Some(AirportCode::try_new(airport)?);
        Ok(self)
    }

    pub fn date(mut self, date: impl Into<String>) -> Self {
        self.date = Some(date.into());
        self
    }

    pub fn number_of_connections(mut self, connections: u32) -> Self {
        self.number_of_connections = Some(connections);
        self
    }

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

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

    pub fn fare_basis(mut self, fare: FareBasis) -> Self {
        self.fare_basis = Some(fare);
        self
    }

    pub fn fare_basis_str(mut self, fare: impl Into<String>) -> Result<Self, ValidationError> {
        self.fare_basis = Some(FareBasis::try_new(fare)?);
        Ok(self)
    }

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

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

    pub fn build(self) -> Result<TravelLeg, ValidationError> {
        let date = self
            .date
            .ok_or_else(|| ValidationError::EmptyField("travel_date".to_string()))?;

        // Validar formato da data (ISO 8601: YYYY-MM-DDThh:mmZ)
        if date.len() > 17 {
            return Err(ValidationError::TooLong {
                field: "travel_date".to_string(),
                max: 17,
                actual: date.len(),
            });
        }

        Ok(TravelLeg {
            origin_city: self.origin_city,
            destination_city: self.destination_city,
            origin_airport: self.origin_airport,
            destination_airport: self.destination_airport,
            date,
            number_of_connections: self.number_of_connections,
            class: self.class,
            fare_basis: self.fare_basis,
            company: self.company,
        })
    }
}

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

    #[test]
    fn test_travel_leg_flight() {
        let leg = TravelLeg::builder()
            .origin_airport_str("GRU")
            .unwrap()
            .destination_airport_str("JFK")
            .unwrap()
            .date("2018-12-25T18:00Z")
            .class_str("economy")
            .unwrap()
            .number_of_connections(1)
            .fare_basis_str("Y")
            .unwrap()
            .company_str("LATAM")
            .unwrap()
            .build()
            .unwrap();

        assert_eq!(leg.origin_airport.unwrap().as_str(), "GRU");
        assert_eq!(leg.destination_airport.unwrap().as_str(), "JFK");
        assert_eq!(leg.date, "2018-12-25T18:00Z");
    }

    #[test]
    fn test_travel_leg_bus() {
        let leg = TravelLeg::builder()
            .origin_city_str("Santos")
            .unwrap()
            .destination_city_str("São Paulo")
            .unwrap()
            .date("2018-12-25T18:00Z")
            .class_str("economy")
            .unwrap()
            .build()
            .unwrap();

        assert_eq!(leg.origin_city.unwrap().as_str(), "Santos");
        assert_eq!(leg.destination_city.unwrap().as_str(), "São Paulo");
    }

    #[test]
    fn test_travel_leg_missing_date() {
        let result = TravelLeg::builder()
            .origin_airport_str("GRU")
            .unwrap()
            .build();

        assert!(result.is_err());
    }

    #[test]
    fn test_travel_leg_serialization() {
        let leg = TravelLeg::builder()
            .origin_airport_str("GRU")
            .unwrap()
            .destination_airport_str("JFK")
            .unwrap()
            .date("2018-12-25T18:00Z")
            .build()
            .unwrap();

        let json = serde_json::to_string(&leg).unwrap();
        assert!(json.contains("GRU"));
        assert!(json.contains("JFK"));
    }
}

