use super::{BoardBasis, HotelGuest, RoomCode, RoomNumber, RoomType};
use crate::types::shared::date_iso_8601::DateIso8601;
use crate::types::validation_errors::ValidationError;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HotelRoom {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub number: Option<RoomNumber>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<RoomCode>,

    #[serde(rename = "type")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub room_type: Option<RoomType>,
    pub check_in_date: DateIso8601,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub check_out_date: Option<DateIso8601>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub number_of_guests: Option<u32>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub board_basis: Option<BoardBasis>,
    pub guests: Vec<HotelGuest>,
}

impl HotelRoom {
    pub fn builder() -> HotelRoomBuilder {
        HotelRoomBuilder::default()
    }
}

#[derive(Default)]
pub struct HotelRoomBuilder {
    number: Option<RoomNumber>,
    code: Option<RoomCode>,
    room_type: Option<RoomType>,
    check_in_date: Option<DateIso8601>,
    check_out_date: Option<DateIso8601>,
    number_of_guests: Option<u32>,
    board_basis: Option<BoardBasis>,
    guests: Vec<HotelGuest>,
}

impl HotelRoomBuilder {
    pub fn number(mut self, number: RoomNumber) -> Self {
        self.number = Some(number);
        self
    }

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

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

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

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

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

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

    pub fn add_guest(mut self, guest: HotelGuest) -> Self {
        self.guests.push(guest);
        self
    }

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

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

        if self.guests.is_empty() {
            return Err(ValidationError::InvalidFormat {
                field: "guests".to_string(),
                message: "at least one guest is required".to_string(),
            });
        }

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

        if let Some(num_guests) = self.number_of_guests
            && num_guests > 9999
        {
            return Err(ValidationError::InvalidFormat {
                field: "number_of_guests".to_string(),
                message: "number of guests cannot exceed 9999".to_string(),
            });
        }

        Ok(HotelRoom {
            number: self.number,
            code: self.code,
            room_type: self.room_type,
            check_in_date,
            check_out_date: self.check_out_date,
            number_of_guests: self.number_of_guests,
            board_basis: self.board_basis,
            guests: self.guests,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::address::Country;
    use crate::types::hotel::GuestDocumentType;

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

        let check_in_date = DateIso8601::new("2016-02-15").unwrap();
        let room = HotelRoom::builder()
            .number(RoomNumber::try_new("1003").unwrap())
            .code(RoomCode::try_new("STD-001").unwrap())
            .room_type(RoomType::try_new("double").unwrap())
            .check_in_date(check_in_date.clone())
            .check_out_date(DateIso8601::new("2016-02-16").unwrap())
            .number_of_guests(2)
            .board_basis(BoardBasis::try_new("all-inclusive").unwrap())
            .add_guest(guest)
            .build()
            .unwrap();

        assert_eq!(room.number.unwrap(), RoomNumber::try_new("1003").unwrap());
        assert_eq!(room.check_in_date, check_in_date);
        assert_eq!(room.guests.len(), 1);
    }

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

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

        assert_eq!(room.check_in_date, check_in);
        assert!(room.number.is_none());
        assert!(room.room_type.is_none());
    }

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

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

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

        assert_eq!(room.guests.len(), 2);
        assert_eq!(room.number_of_guests, Some(2));
    }

    #[test]
    fn test_hotel_room_no_guests() {
        let result = HotelRoom::builder()
            .check_in_date(DateIso8601::new("2016-02-15").unwrap())
            .build();

        assert!(result.is_err());
    }

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

        let result = HotelRoom::builder().add_guest(guest).build();

        assert!(result.is_err());
    }

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

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

        let json = serde_json::to_string(&room).unwrap();
        assert!(json.contains("101"));
        assert!(json.contains("single"));
        assert!(json.contains("2016-02-15"));
    }
}
