import pytest
from elevator_traffic_analysis.floor import Floor
from elevator_traffic_analysis.passenger import Passenger


class TestFloorCreation:
    """Tests for Floor creation and initialization"""

    def test_floor_creation_basic(self):
        """
        Given: A floor ID is provided
        When: A Floor object is created
        Then: The floor has correct ID and empty passenger lists
        """
        floor = Floor(floor_id="floor__1")
        assert floor.floor_id == "floor__1"
        assert len(floor.waiting_passengers) == 0
        assert len(floor.dismbarked_passengers) == 0

    def test_floor_creation_ground_floor(self):
        """
        Given: Ground floor ID 'floor__0' is provided
        When: A Floor object is created
        Then: The floor number is correctly parsed as 0
        """
        floor = Floor(floor_id="floor__0")
        assert floor.floor_id == "floor__0"
        assert int(floor) == 0

    def test_floor_creation_high_floor(self):
        """
        Given: A high floor ID 'floor__99' is provided
        When: A Floor object is created
        Then: The floor number is correctly parsed as 99
        """
        floor = Floor(floor_id="floor__99")
        assert floor.floor_id == "floor__99"
        assert int(floor) == 99


class TestFloorComparison:
    """Tests for Floor comparison operators"""

    def test_floor_equality(self):
        """
        Given: Two floors with the same floor number
        When: Equality comparison is performed
        Then: The floors are equal
        """
        floor1 = Floor(floor_id="floor__5")
        floor2 = Floor(floor_id="floor__5")
        assert floor1 == floor2

    def test_floor_inequality(self):
        """
        Given: Two floors with different floor numbers
        When: Inequality comparison is performed
        Then: The floors are not equal
        """
        floor1 = Floor(floor_id="floor__3")
        floor2 = Floor(floor_id="floor__7")
        assert floor1 != floor2

    def test_floor_less_than(self):
        """
        Given: Two floors with different floor numbers
        When: Less than comparison is performed
        Then: The lower floor is less than the higher floor
        """
        floor1 = Floor(floor_id="floor__2")
        floor2 = Floor(floor_id="floor__5")
        assert floor1 < floor2
        assert not floor2 < floor1

    def test_floor_greater_than(self):
        """
        Given: Two floors with different floor numbers
        When: Greater than comparison is performed
        Then: The higher floor is greater than the lower floor
        """
        floor1 = Floor(floor_id="floor__8")
        floor2 = Floor(floor_id="floor__3")
        assert floor1 > floor2
        assert not floor2 > floor1

    def test_floor_less_than_or_equal(self):
        """
        Given: Floors with equal and different floor numbers
        When: Less than or equal comparison is performed
        Then: The comparison returns correct results for both cases
        """
        floor1 = Floor(floor_id="floor__4")
        floor2 = Floor(floor_id="floor__4")
        floor3 = Floor(floor_id="floor__6")
        assert floor1 <= floor2
        assert floor1 <= floor3
        assert not floor3 <= floor1

    def test_floor_greater_than_or_equal(self):
        """
        Given: Floors with equal and different floor numbers
        When: Greater than or equal comparison is performed
        Then: The comparison returns correct results for both cases
        """
        floor1 = Floor(floor_id="floor__7")
        floor2 = Floor(floor_id="floor__7")
        floor3 = Floor(floor_id="floor__3")
        assert floor1 >= floor2
        assert floor1 >= floor3
        assert not floor3 >= floor1

    def test_floor_sorting(self):
        """
        Given: A list of floors in random order
        When: The floors are sorted
        Then: The floors are ordered by floor number in ascending order
        """
        floors = [
            Floor(floor_id="floor__5"),
            Floor(floor_id="floor__1"),
            Floor(floor_id="floor__9"),
            Floor(floor_id="floor__2"),
        ]
        sorted_floors = sorted(floors)
        floor_numbers = [int(f) for f in sorted_floors]
        assert floor_numbers == [1, 2, 5, 9]


class TestFloorIntConversion:
    """Tests for Floor to int conversion"""

    def test_int_conversion(self):
        """
        Given: A floor with ID 'floor__42'
        When: The floor is converted to int
        Then: The floor number 42 is returned
        """
        floor = Floor(floor_id="floor__42")
        assert int(floor) == 42

    def test_int_conversion_zero(self):
        """
        Given: Ground floor with ID 'floor__0'
        When: The floor is converted to int
        Then: The floor number 0 is returned
        """
        floor = Floor(floor_id="floor__0")
        assert int(floor) == 0

    def test_int_conversion_double_digit(self):
        """
        Given: A floor with double-digit ID 'floor__15'
        When: The floor is converted to int
        Then: The floor number 15 is returned
        """
        floor = Floor(floor_id="floor__15")
        assert int(floor) == 15


class TestFloorRepresentation:
    """Tests for Floor string representation"""

    def test_repr_empty_floor(self):
        """
        Given: A floor with no waiting passengers
        When: The floor's repr is called
        Then: The representation includes floor ID and zero waiting passengers
        """
        floor = Floor(floor_id="floor__3")
        repr_str = repr(floor)
        assert "Floor" in repr_str
        assert "floor__3" in repr_str
        assert "waiting_passengers=0" in repr_str

    def test_repr_floor_with_passengers(self):
        """
        Given: A floor with two waiting passengers
        When: The floor's repr is called
        Then: The representation includes the count of waiting passengers
        """
        floor = Floor(floor_id="floor__5")
        destination = Floor(floor_id="floor__10")
        floor.create_passenger("passenger_1", destination, 0)
        floor.create_passenger("passenger_2", destination, 0)
        repr_str = repr(floor)
        assert "floor__5" in repr_str
        assert "waiting_passengers=2" in repr_str


class TestFloorToDictSerialization:
    """Tests for Floor to_dict method"""

    def test_to_dict_empty_floor(self):
        """
        Given: A floor with no passengers
        When: to_dict is called
        Then: A dictionary with zero passenger counts is returned
        """
        floor = Floor(floor_id="floor__7")
        floor_dict = floor.to_dict()

        assert floor_dict['type'] == 'Floor'
        assert floor_dict['id'] == 'floor__7'
        assert floor_dict['waiting_passengers_count'] == 0
        assert floor_dict['dismbarked_passengers_count'] == 0
        assert len(floor_dict['waiting_passengers']) == 0
        assert len(floor_dict['dismbarked_passengers']) == 0

    def test_to_dict_with_waiting_passengers(self):
        """
        Given: A floor with waiting passengers
        When: to_dict is called
        Then: The dictionary includes waiting passenger data
        """
        floor = Floor(floor_id="floor__3")
        destination = Floor(floor_id="floor__8")
        passenger1 = floor.create_passenger("passenger_1", destination, 10)
        passenger2 = floor.create_passenger("passenger_2", destination, 15)

        floor_dict = floor.to_dict()

        assert floor_dict['waiting_passengers_count'] == 2
        assert floor_dict['dismbarked_passengers_count'] == 0
        assert len(floor_dict['waiting_passengers']) == 2
        assert floor_dict['waiting_passengers'][0]['id'] == 'passenger_1'
        assert floor_dict['waiting_passengers'][1]['id'] == 'passenger_2'

    def test_to_dict_with_dismbarked_passengers(self):
        """
        Given: A floor with dismbarked passengers
        When: to_dict is called
        Then: The dictionary includes dismbarked passenger data
        """
        floor = Floor(floor_id="floor__5")
        destination = Floor(floor_id="floor__10")
        passenger = Passenger("passenger_1", floor, destination, 0)
        floor.add_dismbarked_passenger(passenger)

        floor_dict = floor.to_dict()

        assert floor_dict['dismbarked_passengers_count'] == 1
        assert len(floor_dict['dismbarked_passengers']) == 1
        assert floor_dict['dismbarked_passengers'][0]['id'] == 'passenger_1'


class TestPassengerManagement:
    """Tests for managing passengers on floors"""

    def test_add_waiting_passenger(self):
        """
        Given: A passenger is created
        When: The passenger is added to the floor's waiting list
        Then: The passenger appears in the waiting_passengers list
        """
        origin_floor = Floor(floor_id="floor__1")
        destination_floor = Floor(floor_id="floor__5")
        passenger = Passenger("passenger_1", origin_floor, destination_floor, 10)

        origin_floor.add_waiting_passenger(passenger)

        assert len(origin_floor.waiting_passengers) == 1
        assert passenger in origin_floor.waiting_passengers

    def test_remove_waiting_passenger(self):
        """
        Given: A passenger is in the floor's waiting list
        When: The passenger is removed from the waiting list
        Then: The passenger is no longer in the waiting_passengers list
        """
        origin_floor = Floor(floor_id="floor__2")
        destination_floor = Floor(floor_id="floor__6")
        passenger = Passenger("passenger_1", origin_floor, destination_floor, 10)

        origin_floor.add_waiting_passenger(passenger)
        assert len(origin_floor.waiting_passengers) == 1

        origin_floor.remove_waiting_passenger(passenger)
        assert len(origin_floor.waiting_passengers) == 0
        assert passenger not in origin_floor.waiting_passengers

    def test_remove_waiting_passenger_raises_error_if_not_present(self):
        """
        Given: A passenger that is not in the floor's waiting list
        When: Attempting to remove the passenger
        Then: ValueError is raised
        """
        origin_floor = Floor(floor_id="floor__3")
        destination_floor = Floor(floor_id="floor__7")
        passenger = Passenger("passenger_1", origin_floor, destination_floor, 10)

        with pytest.raises(ValueError):
            origin_floor.remove_waiting_passenger(passenger)

    def test_add_dismbarked_passenger(self):
        """
        Given: A passenger has completed their journey
        When: The passenger is added to the destination floor's dismbarked list
        Then: The passenger appears in the dismbarked_passengers list
        """
        origin_floor = Floor(floor_id="floor__1")
        destination_floor = Floor(floor_id="floor__5")
        passenger = Passenger("passenger_1", origin_floor, destination_floor, 10)

        destination_floor.add_dismbarked_passenger(passenger)

        assert len(destination_floor.dismbarked_passengers) == 1
        assert passenger in destination_floor.dismbarked_passengers

    def test_multiple_waiting_passengers(self):
        """
        Given: Multiple passengers are created
        When: All passengers are added to the floor's waiting list
        Then: All passengers appear in the waiting_passengers list
        """
        floor = Floor(floor_id="floor__0")
        destination = Floor(floor_id="floor__10")

        passengers = [
            Passenger(f"passenger_{i}", floor, destination, i * 10)
            for i in range(5)
        ]

        for passenger in passengers:
            floor.add_waiting_passenger(passenger)

        assert len(floor.waiting_passengers) == 5
        for passenger in passengers:
            assert passenger in floor.waiting_passengers

    def test_multiple_dismbarked_passengers(self):
        """
        Given: Multiple passengers have completed their journey
        When: All passengers are added to the dismbarked list
        Then: All passengers appear in the dismbarked_passengers list
        """
        origin = Floor(floor_id="floor__0")
        destination = Floor(floor_id="floor__8")

        passengers = [
            Passenger(f"passenger_{i}", origin, destination, i * 10)
            for i in range(3)
        ]

        for passenger in passengers:
            destination.add_dismbarked_passenger(passenger)

        assert len(destination.dismbarked_passengers) == 3
        for passenger in passengers:
            assert passenger in destination.dismbarked_passengers


class TestCreatePassenger:
    """Tests for the create_passenger method"""

    def test_create_passenger_basic(self):
        """
        Given: Origin and destination floors exist
        When: create_passenger is called on the origin floor
        Then: A passenger is created with correct properties
        """
        origin_floor = Floor(floor_id="floor__0")
        destination_floor = Floor(floor_id="floor__5")

        passenger = origin_floor.create_passenger(
            passenger_id="passenger_1",
            destination_floor=destination_floor,
            tick_appeared=10
        )

        assert passenger.passenger_id == "passenger_1"
        assert passenger.origin_floor == origin_floor
        assert passenger.destination_floor == destination_floor
        assert passenger.tick_appeared == 10
        assert passenger.state == "WAITING"

    def test_create_passenger_adds_to_waiting_list(self):
        """
        Given: A passenger is created via create_passenger
        When: The method completes
        Then: The passenger is automatically added to the floor's waiting list
        """
        origin_floor = Floor(floor_id="floor__2")
        destination_floor = Floor(floor_id="floor__8")

        passenger = origin_floor.create_passenger(
            passenger_id="passenger_1",
            destination_floor=destination_floor,
            tick_appeared=5
        )

        assert len(origin_floor.waiting_passengers) == 1
        assert passenger in origin_floor.waiting_passengers

    def test_create_multiple_passengers(self):
        """
        Given: An origin floor
        When: Multiple passengers are created via create_passenger
        Then: All passengers are added to the waiting list
        """
        origin_floor = Floor(floor_id="floor__1")
        destination_floor = Floor(floor_id="floor__9")

        passenger1 = origin_floor.create_passenger("passenger_1", destination_floor, 0)
        passenger2 = origin_floor.create_passenger("passenger_2", destination_floor, 5)
        passenger3 = origin_floor.create_passenger("passenger_3", destination_floor, 10)

        assert len(origin_floor.waiting_passengers) == 3
        assert passenger1 in origin_floor.waiting_passengers
        assert passenger2 in origin_floor.waiting_passengers
        assert passenger3 in origin_floor.waiting_passengers

    def test_create_passenger_returns_passenger_object(self):
        """
        Given: create_passenger is called
        When: The method completes
        Then: A Passenger instance is returned
        """
        origin_floor = Floor(floor_id="floor__3")
        destination_floor = Floor(floor_id="floor__6")

        passenger = origin_floor.create_passenger("passenger_1", destination_floor, 20)

        assert isinstance(passenger, Passenger)


class TestFloorIntegration:
    """Integration tests for Floor with passenger workflows"""

    def test_passenger_lifecycle_on_floors(self):
        """
        Given: A passenger is created on an origin floor
        When: The passenger embarks, travels, and disembarks
        Then: The passenger transitions correctly through all states and floors
        """
        origin_floor = Floor(floor_id="floor__0")
        destination_floor = Floor(floor_id="floor__10")

        # Create passenger
        passenger = origin_floor.create_passenger("passenger_1", destination_floor, 0)
        assert len(origin_floor.waiting_passengers) == 1
        assert len(destination_floor.waiting_passengers) == 0

        # Embark passenger (remove from waiting)
        origin_floor.remove_waiting_passenger(passenger)
        passenger.embark(5)
        assert len(origin_floor.waiting_passengers) == 0
        assert passenger.state == "EMBARKED"

        # Disembark passenger
        passenger.disembark(15)
        destination_floor.add_dismbarked_passenger(passenger)
        assert len(destination_floor.dismbarked_passengers) == 1
        assert passenger.state == "DISEMBARKED"

    def test_multiple_passengers_different_destinations(self):
        """
        Given: Multiple passengers are created on the same floor
        When: Each passenger has a different destination floor
        Then: All passengers are correctly assigned their respective destinations
        """
        origin_floor = Floor(floor_id="floor__0")
        dest_floor_5 = Floor(floor_id="floor__5")
        dest_floor_10 = Floor(floor_id="floor__10")
        dest_floor_15 = Floor(floor_id="floor__15")

        passenger1 = origin_floor.create_passenger("passenger_1", dest_floor_5, 0)
        passenger2 = origin_floor.create_passenger("passenger_2", dest_floor_10, 2)
        passenger3 = origin_floor.create_passenger("passenger_3", dest_floor_15, 4)

        assert len(origin_floor.waiting_passengers) == 3
        assert passenger1.destination_floor == dest_floor_5
        assert passenger2.destination_floor == dest_floor_10
        assert passenger3.destination_floor == dest_floor_15

    def test_floor_serialization_with_full_passenger_data(self):
        """
        Given: A floor with both waiting and dismbarked passengers
        When: to_dict is called
        Then: The dictionary includes complete data for all passengers
        """
        origin_floor = Floor(floor_id="floor__1")
        destination_floor = Floor(floor_id="floor__8")

        passenger1 = origin_floor.create_passenger("passenger_1", destination_floor, 10)
        passenger2 = Passenger("passenger_2", origin_floor, destination_floor, 5)
        passenger2.embark(15)
        passenger2.disembark(25)
        origin_floor.add_dismbarked_passenger(passenger2)

        floor_dict = origin_floor.to_dict()

        assert floor_dict['waiting_passengers_count'] == 1
        assert floor_dict['dismbarked_passengers_count'] == 1
        assert floor_dict['waiting_passengers'][0]['state'] == 'WAITING'
        assert floor_dict['dismbarked_passengers'][0]['state'] == 'DISEMBARKED'
