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

use super::{HotelCategory, HotelName, HotelRoom};

/// Dados do hotel
///
/// Ref: <https://docs.konduto.com/reference/obj-hotel>
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Hotel {
    /// Nome do hotel (obrigatório)
    pub name: HotelName,

    /// Endereço do hotel (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address1: Option<AddressLine>,

    /// Complemento do endereço (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address2: 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>,

    /// Categoria do hotel (recomendado)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<HotelCategory>,

    /// Lista de quartos (obrigatório, max 20)
    pub rooms: Vec<HotelRoom>,
}

impl Hotel {
    /// Cria um builder para construir um hotel
    pub fn builder() -> HotelBuilder {
        HotelBuilder::default()
    }
}

/// Builder para criar hotéis
#[derive(Default)]
pub struct HotelBuilder {
    name: Option<HotelName>,
    address1: Option<AddressLine>,
    address2: Option<AddressLine>,
    city: Option<City>,
    state: Option<State>,
    zip: Option<ZipCode>,
    country: Option<Country>,
    category: Option<HotelCategory>,
    rooms: Vec<HotelRoom>,
}

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

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

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

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

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

    pub fn address2_str(mut self, address: impl Into<String>) -> Result<Self, ValidationError> {
        self.address2 = 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 category(mut self, category: HotelCategory) -> Self {
        self.category = Some(category);
        self
    }

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

    pub fn add_room(mut self, room: HotelRoom) -> Self {
        self.rooms.push(room);
        self
    }

    pub fn rooms(mut self, rooms: Vec<HotelRoom>) -> Self {
        self.rooms = rooms;
        self
    }

    pub fn build(self) -> Result<Hotel, ValidationError> {
        if self.rooms.is_empty() {
            return Err(ValidationError::InvalidFormat {
                field: "rooms".to_string(),
                message: "at least one room is required".to_string(),
            });
        }

        if self.rooms.len() > 20 {
            return Err(ValidationError::InvalidFormat {
                field: "rooms".to_string(),
                message: format!("maximum 20 rooms allowed, received {}", self.rooms.len()),
            });
        }

        Ok(Hotel {
            name: self
                .name
                .ok_or_else(|| ValidationError::EmptyField("hotel_name".to_string()))?,
            address1: self.address1,
            address2: self.address2,
            city: self.city,
            state: self.state,
            zip: self.zip,
            country: self.country,
            category: self.category,
            rooms: self.rooms,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::hotel::{GuestDocumentType, HotelGuest};
    use crate::types::shared::date_iso_8601::DateIso8601;
    use crate::{RoomNumber, RoomType};

    #[test]
    fn test_hotel_complete() {
        let guest = HotelGuest::builder()
            .name_str("Mary Jane")
            .unwrap()
            .document_str("6253107")
            .unwrap()
            .document_type(GuestDocumentType::Id)
            .nationality(Country::BR)
            .build()
            .unwrap();

        let room = HotelRoom::builder()
            .number(RoomNumber::try_new("1003").unwrap())
            .room_type(RoomType::try_new("double").unwrap())
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest)
            .build()
            .unwrap();

        let hotel = Hotel::builder()
            .name_str("Hotel Konduto")
            .unwrap()
            .address1_str("Rod Três Lagoas, 46")
            .unwrap()
            .address2_str("Vidinha")
            .unwrap()
            .city_str("Pouso Alto")
            .unwrap()
            .state_str("MG")
            .unwrap()
            .zip_str("37468-000")
            .unwrap()
            .country(Country::BR)
            .category_str("5 estrelas")
            .unwrap()
            .add_room(room)
            .build()
            .unwrap();

        assert_eq!(hotel.name.as_str(), "Hotel Konduto");
        assert_eq!(hotel.city.as_ref().unwrap().as_str(), "Pouso Alto");
        assert_eq!(hotel.rooms.len(), 1);
    }

    #[test]
    fn test_hotel_minimal() {
        let guest = HotelGuest::builder()
            .name_str("Test Guest")
            .unwrap()
            .build()
            .unwrap();

        let room = HotelRoom::builder()
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest)
            .build()
            .unwrap();
        let hotel = Hotel::builder()
            .name_str("Simple Hotel")
            .unwrap()
            .add_room(room)
            .build()
            .unwrap();

        assert_eq!(hotel.name.as_str(), "Simple Hotel");
        assert!(hotel.address1.is_none());
        assert!(hotel.category.is_none());
    }

    #[test]
    fn test_hotel_multiple_rooms() {
        let guest1 = HotelGuest::builder()
            .name_str("Guest 1")
            .unwrap()
            .build()
            .unwrap();

        let guest2 = HotelGuest::builder()
            .name_str("Guest 2")
            .unwrap()
            .build()
            .unwrap();

        let room1 = HotelRoom::builder()
            .number(RoomNumber::try_new("101").unwrap())
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest1)
            .build()
            .unwrap();

        let room2 = HotelRoom::builder()
            .number(RoomNumber::try_new("102").unwrap())
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest2)
            .build()
            .unwrap();

        let hotel = Hotel::builder()
            .name_str("Multi Room Hotel")
            .unwrap()
            .add_room(room1)
            .add_room(room2)
            .build()
            .unwrap();

        assert_eq!(hotel.rooms.len(), 2);
    }

    #[test]
    fn test_hotel_no_rooms() {
        let result = Hotel::builder().name_str("Empty Hotel").unwrap().build();

        assert!(result.is_err());
    }

    #[test]
    fn test_hotel_missing_name() {
        let guest = HotelGuest::builder()
            .name_str("Test")
            .unwrap()
            .build()
            .unwrap();

        let room = HotelRoom::builder()
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest)
            .build()
            .unwrap();

        let result = Hotel::builder().add_room(room).build();

        assert!(result.is_err());
    }

    #[test]
    fn test_hotel_serialization() {
        let guest = HotelGuest::builder()
            .name_str("Test Guest")
            .unwrap()
            .build()
            .unwrap();

        let room = HotelRoom::builder()
            .number(RoomNumber::try_new("101").unwrap())
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest)
            .build()
            .unwrap();

        let hotel = Hotel::builder()
            .name_str("Test Hotel")
            .unwrap()
            .city_str("São Paulo")
            .unwrap()
            .country(Country::BR)
            .add_room(room)
            .build()
            .unwrap();

        let json = serde_json::to_string_pretty(&hotel).unwrap();
        assert!(json.contains("Test Hotel"));
        assert!(json.contains("São Paulo"));
        assert!(json.contains("BR"));
    }

    #[test]
    fn test_hotel_deserialization() {
        let json = r#"{
            "name": "Hotel Konduto",
            "address1": "Rod Três Lagoas, 46",
            "city": "Pouso Alto",
            "state": "MG",
            "country": "BR",
            "rooms": [
                {
                    "number": "1003",
                    "type": "double",
                    "check_in_date": "2016-02-15",
                    "guests": [
                        {
                            "name": "Mary Jane",
                            "document": "6253107",
                            "document_type": "id",
                            "nationality": "BR"
                        }
                    ]
                }
            ]
        }"#;

        let hotel: Hotel = serde_json::from_str(json).unwrap();
        assert_eq!(hotel.name.as_str(), "Hotel Konduto");
        assert_eq!(hotel.rooms.len(), 1);
        assert_eq!(hotel.rooms[0].guests.len(), 1);
    }

    #[test]
    fn test_hotel_roundtrip() {
        let guest = HotelGuest::builder()
            .name_str("Test")
            .unwrap()
            .build()
            .unwrap();

        let room = HotelRoom::builder()
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .add_guest(guest)
            .build()
            .unwrap();

        let original = Hotel::builder()
            .name_str("Test Hotel")
            .unwrap()
            .add_room(room)
            .build()
            .unwrap();

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: Hotel = serde_json::from_str(&json).unwrap();

        assert_eq!(original, deserialized);
    }
}
