import pandas as pd
import pytest
from unittest.mock import Mock
from backend.Property import Property
from etl.epc_clean.EpcClean import EpcClean
from etl.epc.Record import EPCRecord

# Define some test data
mock_epc_response = {
    "rows": [
        {
            "tenure": "rental (social)",
            "lmk-key": 1,
            "uprn": 1,
            "number-habitable-rooms": 5,
            "property-type": "House",
            "built-form": "Detached",
            "inspection-date": "2023-06-01",
            'lodgement-datetime': '2023-06-01 20:29:01',
            "some-other-key": "some-value",
            "roof-description": "pitched, no insulation",
            "walls-description": "Walls Description",
            "windows-description": "Windows Description",
            "mainheat-description": "Main Heating Description",
            "hotwater-description": "Hot Water Description",
            "transaction-type": "rental",
            "lighting-description": "Good Lighting Efficiency",
            "energy-consumption-current": "50",
            "co2-emissions-current": "123",
            "mechanical-ventilation": "natural",
            'photo-supply': 0,
            "solar-water-heating-flag": "N",
            "wind-turbine-count": 0,
            "extension-count": 0,
            "heat-loss-corridor": "no corridor",
            "unheated-corridor-length": 0,
            "mains-gas-flag": "Y",
            "floor-height": 2.5,
            "total-floor-area": 100,
            "construction-age-band": "England and Wales: 1967-1975",
            "floor-description": "Floor Description",
            "floor-level": "Ground"
        },
        {
            "lmk-key": 2,
            "uprn": 2,
            "number-habitable-rooms": 5,
            "property-type": "House",
            "built-form": "Detached",
            "inspection-date": "2023-05-01",
            'lodgement-datetime': '2023-05-01 20:29:01',
            "some-other-key": "some-other-value",
            "roof-description": "Roof Description",
            "walls-description": "Walls Description",
            "windows-description": "Windows Description",
            "mainheat-description": "Main Heating Description",
            "hotwater-description": "Hot Water Description",
            "transaction-type": "rental",
            "lighting-description": "Good Lighting Efficiency",
            "energy-consumption-current": "50",
            "co2-emissions-current": "123",
            "mechanical-ventilation": "natural",
            'photo-supply': 0,
            "solar-water-heating-flag": "N",
            "wind-turbine-count": 0,
            "extension-count": 0,
            "heat-loss-corridor": "no corridor",
            "unheated-corridor-length": 0,
            "mains-gas-flag": "Y",
            "floor-height": 2.5,
            "total-floor-area": 100,
            "construction-age-band": "England and Wales: 1967-1975",
            "floor-description": "Floor Description",
            "floor-level": "Ground"
        }
    ]
}

mock_epc_response_dupe = {
    'rows': [
        {
            "lmk-key": 1,
            "uprn": 1,
            "number-habitable-rooms": 5,
            "property-type": "House",
            'inspection-date': '2023-06-01',
            'lodgement-datetime': '2023-06-01 20:29:01',
            'some-other-key': 'some-value', 'roof-description': 'Roof Description',
            'walls-description': 'Walls Description', 'windows-description': 'Windows Description',
            'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description',
            "transaction-type": "rental",
            "lighting-description": "Good Lighting Efficiency",
            "energy-consumption-current": "50",
            "co2-emissions-current": "123",
            "mechanical-ventilation": "natural",
            'photo-supply': 0,
            "solar-water-heating-flag": "N",
            "wind-turbine-count": 0,
            "extension-count": 0,
            "heat-loss-corridor": "no corridor",
            "unheated-corridor-length": 0,
            "mains-gas-flag": "Y",
            "floor-height": 2.5,
            "total-floor-area": 100,
            "construction-age-band": "England and Wales: 1967-1975",
            "floor-description": "Floor Description",
            "floor-level": "Ground"
        },
        {
            "lmk-key": 2,
            "uprn": 2,
            "number-habitable-rooms": 5,
            "property-type": "House",
            'inspection-date': '2023-05-01',
            'lodgement-datetime': '2023-05-01 20:29:01',
            'some-other-key': 'some-other-value',
            'roof-description': 'Roof Description', 'walls-description': 'Walls Description',
            'windows-description': 'Windows Description', 'mainheat-description': 'Main Heating Description',
            'hotwater-description': 'Hot Water Description',
            "transaction-type": "rental",
            "lighting-description": "Good Lighting Efficiency",
            "energy-consumption-current": "50",
            "co2-emissions-current": "123",
            "mechanical-ventilation": "natural",
            'photo-supply': 0,
            "solar-water-heating-flag": "N",
            "wind-turbine-count": 0,
            "extension-count": 0,
            "heat-loss-corridor": "no corridor",
            "unheated-corridor-length": 0,
            "mains-gas-flag": "Y",
            "floor-height": 2.5,
            "total-floor-area": 100,
            "construction-age-band": "England and Wales: 1967-1975",
            "floor-description": "Floor Description",
            "floor-level": "Ground"
        },
        {
            "lmk-key": 3,
            "uprn": 3,
            "number-habitable-rooms": 5,
            "property-type": "House",
            'inspection-date': '2023-06-01',
            'lodgement-datetime': '2023-06-01 20:29:01',
            'some-other-key': 'duplicate-date',
            'roof-description': 'Roof Description',
            'walls-description': 'Walls Description', 'windows-description': 'Windows Description',
            'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description',
            "transaction-type": "rental",
            "lighting-description": "Good Lighting Efficiency",
            "energy-consumption-current": "50",
            "co2-emissions-current": "123",
            "mechanical-ventilation": "natural",
            'photo-supply': 0,
            "solar-water-heating-flag": "N",
            "wind-turbine-count": 0,
            "extension-count": 0,
            "heat-loss-corridor": "no corridor",
            "unheated-corridor-length": 0,
            "mains-gas-flag": "Y",
            "floor-height": 2.5,
            "total-floor-area": 100,
            "construction-age-band": "England and Wales: 1967-1975",
            "floor-description": "Floor Description",
            "floor-level": "Ground"
        }
    ]
}


class TestProperty:

    @pytest.fixture(autouse=True)
    def mock_photo_supply_lookup(self):
        return pd.DataFrame(
            [
                dict(
                    tenure="rental (social)",
                    built_form="Detached",
                    property_type="House",
                    construction_age_band="England and Wales: 1967-1975",
                    is_flat=False,
                    is_pitched=True,
                    is_roof_room=False,
                    floor_area_decile=2,
                    photo_supply_median=40
                )
            ]
        )

    @pytest.fixture(autouse=True)
    def mock_floor_area_decile_thresholds(self):
        return pd.DataFrame(
            {"floor_area_decile_thresholds": [0, 10, 30, 50]}
        )

    @pytest.fixture(autouse=True)
    def property_instance(self, mock_cleaner):
        epc_record = EPCRecord()
        epc_record.prepared_epc = mock_epc_response["rows"][0]

        property_instance = Property(id=1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        property_instance.number_of_floors = 2
        property_instance.number_of_rooms = 5
        property_instance.floor_area = 100
        property_instance.floor_height = 2.5
        return property_instance

    @pytest.fixture(autouse=True)
    def property_instance_dupe_data(self):
        epc_record = EPCRecord()
        epc_record.prepared_epc = mock_epc_response_dupe["rows"][0]
        property_instance_dupe_data = Property(id=2, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        return property_instance_dupe_data

    # @pytest.fixture
    # def mock_epc_client(self):
    #     mock_epc_client = Mock(spec=EpcClient(auth_token="mocked_auth_token"))
    #     mock_epc_client.domestic.search.return_value = mock_epc_response.copy()
    #     mock_epc_client.auth_token = "mocked_auth_token"
    #     return mock_epc_client
    #
    # @pytest.fixture
    # def mock_epc_client_dupe_data(self):
    #     mock_epc_client_dupe_data = Mock(spec=EpcClient(auth_token="mocked_auth_token"))
    #     mock_epc_client_dupe_data.domestic.search.return_value = mock_epc_response_dupe.copy()
    #     mock_epc_client_dupe_data.auth_token = "mocked_auth_token"
    #     return mock_epc_client_dupe_data

    @pytest.fixture
    def mock_cleaner(self):
        lighting_averages = [
            {'lighting-description': 'good lighting efficiency', 'low-energy-lighting': 99.26666666666667},
            {'lighting-description': 'excellent lighting efficiency', 'low-energy-lighting': 100.0},
            {'lighting-description': 'below average lighting efficiency', 'low-energy-lighting': 0.0}
        ]

        cleaner_spec = EpcClean(
            data=[
                {"roof-description": "Roof Description"},
                {"walls-description": "Walls Description"},
                {"windows-description": "Windows Description"},
                {"mainheat-description": "Main Heating Description"},
                {"hotwater-description": "Hot Water Description"},
                {"lighting-description": "Good Lighting Efficiency"},
                {"low-energy-lighting": 0},
                {"floor-description": "Floor Description"}
            ],
            lighting_averages=lighting_averages
        )

        mock_cleaner = Mock(spec=cleaner_spec)

        walls_data = {
            "original_description": "Walls Description",
            "is_cavity_wall": True,
            "is_solid_brick": False,
            "is_timber_frame": False,
            "is_system_built": False,
            "is_park_home": False,
            "is_cob": False,
            "is_sandstone_or_limestone": False,
            "is_granite_or_whinstone": False,
        }

        mock_cleaner.cleaned = {
            "roof-description": [
                {"original_description": "Roof Description"},
                {"original_description": "pitched, no insulation", "is_pitched": True, "is_flat": False,
                 "is_roof_room": False}
            ],
            "walls-description": [walls_data],
            "windows-description": [{"original_description": "Windows Description"}],
            "mainheat-description": [{"original_description": "Main Heating Description"}],
            "hotwater-description": [{"original_description": "Hot Water Description"}],
            "lighting-description": [{"original_description": "Good Lighting Efficiency"}],
            "floor-description": [
                {"original_description": "Floor Description", "is_suspended": True, "another_property_below": False}]
        }
        return mock_cleaner

    def test_init(self):
        epc_record = EPCRecord()
        epc_record.prepared_epc = {"uprn": 1}
        inst1 = Property(0, postcode="AB12CD", address="Test Address", epc_record=epc_record)

        assert inst1.data is not None

        inst2 = Property(3, "AB12CD", "Test Address", epc_record=epc_record)
        assert inst2.id == 3

        inst3 = Property(4, "AB12CD", "Test Address", epc_record=epc_record)
        assert inst3.data == {"uprn": 1}

    def test_get_components(
        self, property_instance, mock_cleaner, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
    ):
        property_instance.get_components(
            mock_cleaner.cleaned,
            photo_supply_lookup=mock_photo_supply_lookup,
            floor_area_decile_thresholds=mock_floor_area_decile_thresholds
        )

        # Verify that the components are set correctly
        assert property_instance.roof == {
            'original_description': 'pitched, no insulation', 'is_pitched': True,
            'is_flat': False, 'is_roof_room': False
        }

        assert property_instance.walls == {
            "original_description": "Walls Description",
            "is_cavity_wall": True,
            "is_solid_brick": False,
            "is_timber_frame": False,
            "is_system_built": False,
            "is_park_home": False,
            "is_cob": False,
            "is_sandstone_or_limestone": False,
            "is_granite_or_whinstone": False,
        }
        assert property_instance.windows == {"original_description": "Windows Description"}
        assert property_instance.main_heating == {"original_description": "Main Heating Description"}
        assert property_instance.hotwater == {"original_description": "Hot Water Description"}

        assert property_instance.wall_type == "cavity"

    def test_get_components_without_cleaned_data(self, property_instance, mock_cleaner):
        # Modify the mock EpcClean to not have cleaned data
        mock_cleaner.cleaned = {}

        # Verify that ValueError is raised when EpcClean doesn't contain cleaned data
        with pytest.raises(ValueError, match="Cleaner does not contain cleaned data"):
            property_instance.get_components(mock_cleaner.cleaned, pd.DataFrame(), pd.DataFrame())

    def test_get_components_no_attributes(
        self, property_instance, mock_cleaner, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
    ):
        # Modify the mock cleaner to have no attributes for a specific description
        mock_cleaner.cleaned = {
            "roof-description": []
        }
        property_instance.data["roof-description"] = "Pitched, no insulation"
        property_instance.walls = {
            "original_description": "Walls Description",
            "is_cavity_wall": True,
            "is_solid_brick": False,
            "is_timber_frame": False,
            "is_system_built": False,
            "is_park_home": False,
            "is_cob": False,
            "is_sandstone_or_limestone": False,
            "is_granite_or_whinstone": False,
        }

        property_instance.floor = {
            "is_suspended": False,
            "another_property_below": False,
            "is_solid": True
        }

        # Assert backup cleaning has been applied
        property_instance.get_components(
            mock_cleaner.cleaned, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
        )

        assert property_instance.roof["clean_description"] == "Pitched, no insulation"
        assert property_instance.roof["is_pitched"]

    def test_get_components_multiple_attributes(
        self, property_instance, mock_cleaner, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
    ):
        # This shouldn't happen - it would mean a cleaning error
        property_instance.data["roof-description"] = "Roof Description"
        cleaned = {
            "roof-description": [
                {"original_description": "Roof Description"},
                {"original_description": "Roof Description"}
            ]
        }

        # Verify that ValueError is raised when multiple attributes are found
        with pytest.raises(ValueError, match="Either No attributes or multiple found for roof-description"):
            property_instance.get_components(cleaned, mock_photo_supply_lookup, mock_floor_area_decile_thresholds)

    def test_set_spatial(self):
        epc_record = EPCRecord()
        epc_record.prepared_epc = mock_epc_response["rows"][0]
        prop = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)

        spatial1 = pd.DataFrame([{
            'X_COORDINATE': 411143.0, 'Y_COORDINATE': 281701.0, 'LATITUDE': 52.4331896, 'LONGITUDE': -1.8375238,
            'conservation_status': True, 'is_listed_building': False, 'is_heritage_building': True
        }])

        prop.set_spatial(spatial1)

        assert prop.in_conservation_area
        assert not prop.is_listed
        assert prop.is_heritage
        assert prop.restricted_measures

        prop2 = Property(1, "AB12CD", "Test Address", epc_record=epc_record)

        spatial2 = pd.DataFrame([{
            'X_COORDINATE': 411143.0, 'Y_COORDINATE': 281701.0, 'LATITUDE': 52.4331896, 'LONGITUDE': -1.8375238,
            'conservation_status': None, 'is_listed_building': False, 'is_heritage_building': False
        }])

        prop2.set_spatial(spatial2)

        assert prop2.in_conservation_area is None
        assert not prop2.is_listed
        assert not prop2.is_heritage
        assert not prop2.restricted_measures

    def test_set_floor_level(self):
        # In this case, we have a flat which looks looks it's on the first floor, but it's actually on the ground
        # floor, so we should set floor_level to 0
        epc_record = EPCRecord()
        epc_record.prepared_epc = {'floor-level': '01', 'property-type': 'Flat'}
        prop = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        prop.floor = {
            'original_description': 'Solid, no insulation (assumed)', 'clean_description': 'Solid, no insulation',
            'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': True,
            'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': True,
            'another_property_below': False, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None,
            'floor_insulation_thickness': 'none'
        }

        prop.set_floor_level()

        assert prop.floor_level == 0

        # This property is labelled as being on the ground floor but actually has another property below
        # so we set floor level to 1
        epc_record = EPCRecord()
        epc_record.prepared_epc = {'floor-level': 'Ground', 'property-type': 'Flat'}
        prop2 = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        prop2.floor = {
            'original_description': '(Another dwelling below)', 'clean_description': 'Solid, no insulation',
            'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': False,
            'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False,
            'another_property_below': True, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None,
            'floor_insulation_thickness': 'none'
        }

        prop2.set_floor_level()

        assert prop2.floor_level == 1

        # this property is correctly labelled as being on the 2nd floor
        epc_record = EPCRecord()
        epc_record.prepared_epc = {'floor-level': '02', 'property-type': 'Flat'}
        prop3 = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        prop3.floor = {
            'original_description': '(Another dwelling below)', 'clean_description': 'Solid, no insulation',
            'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': False,
            'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False,
            'another_property_below': True, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None,
            'floor_insulation_thickness': 'none'
        }

        prop3.set_floor_level()

        assert prop3.floor_level == 2

        # Example of a house
        epc_record = EPCRecord()
        epc_record.prepared_epc = {'floor-level': '', 'property-type': 'House'}
        prop4 = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        prop4.floor = {
            'original_description': '(Another dwelling below)', 'clean_description': 'Solid, no insulation',
            'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': False,
            'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False,
            'another_property_below': False, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None,
            'floor_insulation_thickness': 'none'
        }

        prop4.set_floor_level()

        assert prop4.floor_level is None
