use crate::types::address::{AddressLine, City, Country, State, ZipCode};
use crate::types::validation_errors::ValidationError;
use serde::{Deserialize, Serialize};

use super::{Latitude, Longitude, PointOfSaleId, PointOfSaleName};

/// Dados da loja física (Point of Sale)
///
/// Ref: <https://docs.konduto.com/reference/loja-f%C3%ADsica>
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PointOfSale {
    /// ID único da loja física (obrigatório)
    pub id: PointOfSaleId,

    /// Nome completo da loja física (obrigatório)
    pub name: PointOfSaleName,

    /// Latitude da loja física (recomendado, -90 a 90)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lat: Option<Latitude>,

    /// Longitude da loja física (recomendado, -180 a 180)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "long")]
    pub lon: Option<Longitude>,

    /// Endereço da loja física (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<AddressLine>,

    /// Cidade (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub city: Option<City>,

    /// Estado (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state: Option<State>,

    /// CEP (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub zip: Option<ZipCode>,

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

impl PointOfSale {
    /// Cria um builder para construir uma loja física
    pub fn builder() -> PointOfSaleBuilder {
        PointOfSaleBuilder::default()
    }
}

/// Builder para criar lojas físicas
#[derive(Default)]
pub struct PointOfSaleBuilder {
    id: Option<PointOfSaleId>,
    name: Option<PointOfSaleName>,
    lat: Option<Latitude>,
    lon: Option<Longitude>,
    address: Option<AddressLine>,
    city: Option<City>,
    state: Option<State>,
    zip: Option<ZipCode>,
    country: Option<Country>,
}

impl PointOfSaleBuilder {
    pub fn id(mut self, id: PointOfSaleId) -> Self {
        self.id = Some(id);
        self
    }

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

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

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

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

    pub fn lat_value(mut self, lat: f64) -> Result<Self, ValidationError> {
        self.lat = Some(Latitude::new(lat)?);
        Ok(self)
    }

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

    pub fn lon_value(mut self, lon: f64) -> Result<Self, ValidationError> {
        self.lon = Some(Longitude::new(lon)?);
        Ok(self)
    }

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

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

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

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

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

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

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

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

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

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

    pub fn build(self) -> Result<PointOfSale, ValidationError> {
        Ok(PointOfSale {
            id: self
                .id
                .ok_or_else(|| ValidationError::EmptyField("point_of_sale_id".to_string()))?,
            name: self
                .name
                .ok_or_else(|| ValidationError::EmptyField("point_of_sale_name".to_string()))?,
            lat: self.lat,
            lon: self.lon,
            address: self.address,
            city: self.city,
            state: self.state,
            zip: self.zip,
            country: self.country,
        })
    }
}

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

    #[test]
    fn test_point_of_sale_complete() {
        let pos = PointOfSale::builder()
            .id_str("000500")
            .unwrap()
            .name_str("Capital Representações")
            .unwrap()
            .lat_value(-23.5677666)
            .unwrap()
            .lon_value(-46.6487763)
            .unwrap()
            .address_str("Rua Dez de Abril, 23")
            .unwrap()
            .city_str("São Paulo")
            .unwrap()
            .state_str("SP")
            .unwrap()
            .zip_str("01001-001")
            .unwrap()
            .country(Country::BR)
            .build()
            .unwrap();

        assert_eq!(pos.id.as_str(), "000500");
        assert_eq!(pos.name.as_str(), "Capital Representações");
        assert_eq!(pos.lat.unwrap().value(), -23.5677666);
        assert_eq!(pos.lon.unwrap().value(), -46.6487763);
        assert_eq!(pos.city.as_ref().unwrap().as_str(), "São Paulo");
    }

    #[test]
    fn test_point_of_sale_minimal() {
        let pos = PointOfSale::builder()
            .id_str("123")
            .unwrap()
            .name_str("Loja Teste")
            .unwrap()
            .build()
            .unwrap();

        assert_eq!(pos.id.as_str(), "123");
        assert_eq!(pos.name.as_str(), "Loja Teste");
        assert!(pos.lat.is_none());
        assert!(pos.lon.is_none());
        assert!(pos.address.is_none());
    }

    #[test]
    fn test_point_of_sale_missing_id() {
        let result = PointOfSale::builder().name_str("Test").unwrap().build();

        assert!(result.is_err());
    }

    #[test]
    fn test_point_of_sale_missing_name() {
        let result = PointOfSale::builder().id_str("123").unwrap().build();

        assert!(result.is_err());
    }

    #[test]
    fn test_point_of_sale_with_coordinates() {
        let pos = PointOfSale::builder()
            .id_str("999")
            .unwrap()
            .name_str("Loja GPS")
            .unwrap()
            .lat_value(-23.5505199)
            .unwrap()
            .lon_value(-46.6333094)
            .unwrap()
            .build()
            .unwrap();

        assert!(pos.lat.is_some());
        assert!(pos.lon.is_some());
    }

    #[test]
    fn test_point_of_sale_invalid_latitude() {
        let result = PointOfSale::builder()
            .id_str("123")
            .unwrap()
            .name_str("Test")
            .unwrap()
            .lat_value(100.0);

        assert!(result.is_err());
    }

    #[test]
    fn test_point_of_sale_invalid_longitude() {
        let result = PointOfSale::builder()
            .id_str("123")
            .unwrap()
            .name_str("Test")
            .unwrap()
            .lon_value(200.0);

        assert!(result.is_err());
    }

    #[test]
    fn test_point_of_sale_serialization() {
        let pos = PointOfSale::builder()
            .id_str("000500")
            .unwrap()
            .name_str("Capital Representações")
            .unwrap()
            .lat_value(-23.5677666)
            .unwrap()
            .lon_value(-46.6487763)
            .unwrap()
            .address_str("Rua Dez de Abril, 23")
            .unwrap()
            .city_str("São Paulo")
            .unwrap()
            .state_str("SP")
            .unwrap()
            .zip_str("01001-001")
            .unwrap()
            .country(Country::BR)
            .build()
            .unwrap();

        let json = serde_json::to_string_pretty(&pos).unwrap();
        assert!(json.contains("000500"));
        assert!(json.contains("Capital Representações"));
        assert!(json.contains("-23.5677666"));
        assert!(json.contains("-46.6487763"));
    }

    #[test]
    fn test_point_of_sale_deserialization() {
        let json = r#"{
            "id": "000500",
            "name": "Capital Representações",
            "lat": -23.5677666,
            "long": -46.6487763,
            "address": "Rua Dez de Abril, 23",
            "city": "São Paulo",
            "state": "SP",
            "zip": "01001-001",
            "country": "BR"
        }"#;

        let pos: PointOfSale = serde_json::from_str(json).unwrap();
        assert_eq!(pos.id.as_str(), "000500");
        assert_eq!(pos.name.as_str(), "Capital Representações");
        assert_eq!(pos.lat.unwrap().value(), -23.5677666);
        assert_eq!(pos.lon.unwrap().value(), -46.6487763);
    }

    #[test]
    fn test_point_of_sale_deserialization_minimal() {
        let json = r#"{
            "id": "123",
            "name": "Loja Simples"
        }"#;

        let pos: PointOfSale = serde_json::from_str(json).unwrap();
        assert_eq!(pos.id.as_str(), "123");
        assert_eq!(pos.name.as_str(), "Loja Simples");
        assert!(pos.lat.is_none());
        assert!(pos.lon.is_none());
    }

    #[test]
    fn test_point_of_sale_roundtrip() {
        let original = PointOfSale::builder()
            .id_str("RT-001")
            .unwrap()
            .name_str("Roundtrip Store")
            .unwrap()
            .lat_value(-23.0)
            .unwrap()
            .lon_value(-46.0)
            .unwrap()
            .city_str("Test City")
            .unwrap()
            .build()
            .unwrap();

        let json = serde_json::to_string(&original).unwrap();
        let restored: PointOfSale = serde_json::from_str(&json).unwrap();

        assert_eq!(original, restored);
    }

    #[test]
    fn test_point_of_sale_clone() {
        let pos = PointOfSale::builder()
            .id_str("1")
            .unwrap()
            .name_str("Clone Test")
            .unwrap()
            .build()
            .unwrap();

        let cloned = pos.clone();
        assert_eq!(pos, cloned);
    }

    #[test]
    fn test_point_of_sale_debug() {
        let pos = PointOfSale::builder()
            .id_str("99")
            .unwrap()
            .name_str("Debug Store")
            .unwrap()
            .build()
            .unwrap();

        let debug_str = format!("{:?}", pos);
        assert!(debug_str.contains("PointOfSale"));
        assert!(debug_str.contains("99"));
    }
}
