from datetime import datetime
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
from etl.bill_savings.KwhData import KwhData

# 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',
            'lodgement-date': '2023-06-01',
            "some-other-key": "some-value",
            "roof-description": "pitched, no insulation",
            "walls-description": "Walls Description",
            "windows-description": "Fully double glazed",
            "mainheat-description": "Boiler and radiators, mains gas",
            "hotwater-description": "From main system",
            "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",
            "lighting-cost-current": 123,
            "heating-cost-current": 800,
            "hot-water-cost-current": 200
        },
        {
            "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',
            'lodgement-date': '2023-05-01',
            "some-other-key": "some-other-value",
            "roof-description": "Roof Description",
            "walls-description": "Walls Description",
            "windows-description": "Fully double glazed",
            "mainheat-description": "Boiler and radiators, mains gas",
            "hotwater-description": "From main system",
            "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",
            "lighting-cost-current": 123,
            "heating-cost-current": 800,
            "hot-water-cost-current": 200
        }
    ]
}


class TestProperty:

    @pytest.fixture(autouse=True)
    def property_instance(self, mock_cleaner):
        epc_record = EPCRecord()
        # Set all required attributes directly on epc_record
        epc_record.uprn = 1
        epc_record.lighting_cost_current = 123
        epc_record.epc_co2_emissions = 5
        epc_record.primary_energy_consumption = 1234
        epc_record.roof_description = "pitched, no insulation"
        epc_record.walls_description = "Walls Description"
        epc_record.windows_description = "Fully double glazed"
        epc_record.mainheat_description = "Boiler and radiators, mains gas"
        epc_record.hotwater_description = "From main system"
        epc_record.floor_description = "Floor Description"
        epc_record.floor_level = "Ground"
        epc_record.property_type = "House"
        # Add any other attributes needed by the tests

        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()
    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': 'Fully double glazed', 'has_glazing': True, 'glazing_coverage': 'full',
                 'glazing_type': 'double', 'no_data': False}
            ],
            "mainheat-description": [
                {
                    'original_description': 'Boiler and radiators, mains gas', 'has_radiators': True,
                    'has_fan_coil_units': False,
                    'has_pipes_in_screed_above_insulation': False, 'has_pipes_in_insulated_timber_floor': False,
                    'has_pipes_in_concrete_slab': False, 'has_boiler': True, 'has_air_source_heat_pump': False,
                    'has_room_heaters': False, 'has_electric_storage_heaters': False, 'has_warm_air': False,
                    'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
                    'has_community_scheme': False,
                    'has_ground_source_heat_pump': False, 'has_no_system_present': False,
                    'has_portable_electric_heaters': False,
                    'has_water_source_heat_pump': False, 'has_electric': False, 'has_mains_gas': True,
                    'has_wood_logs': False,
                    'has_coal': False, 'has_oil': False, 'has_wood_pellets': False, 'has_anthracite': False,
                    'has_dual_fuel_mineral_and_wood': False, 'has_smokeless_fuel': False, 'has_lpg': False,
                    'has_assumed': False,
                    'has_electricaire': False, 'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False,
                    "has_electric_heat_pumps": False,
                    "has_micro-cogeneration": False
                }
            ],
            "hotwater-description": [
                {'original_description': 'From main system', 'heater_type': None, 'system_type': 'from main system',
                 'thermostat_characteristics': None, 'heating_scope': None, 'energy_recovery': None,
                 'tariff_type': None,
                 'extra_features': None, 'chp_systems': None, 'distribution_system': None, 'no_system_present': None,
                 'assumed': False, "appliance": None}
            ],
            "lighting-description": [{"original_description": "Good Lighting Efficiency"}],
            "floor-description": [
                {"original_description": "Floor Description", "is_suspended": True, "another_property_below": False}]
        }
        return mock_cleaner

    @pytest.fixture
    def kwh_client(self):
        kwh_client = KwhData(bucket="retrofit-data-dev", read_consumption_data=False)
        # We fix this pricing table for these tests
        kwh_client.retail_price_comparison = pd.DataFrame(
            [
                {
                    "Date": datetime.today().strftime("%Y-%m-%d"),
                    'Average standard variable tariff (Large legacy suppliers)': 1
                }
            ]
        )
        kwh_client.retail_price_comparison["Date"] = pd.to_datetime(kwh_client.retail_price_comparison["Date"])
        return kwh_client

    def test_init(self):
        epc_record = EPCRecord()
        epc_record.uprn = 1
        epc_record.lighting_cost_current = 123
        epc_record.epc_co2_emissions = 5
        epc_record.primary_energy_consumption = 1234
        epc_record.roof_description = "pitched, no insulation"
        epc_record.walls_description = "Walls Description"
        epc_record.windows_description = "Fully double glazed"
        epc_record.mainheat_description = "Boiler and radiators, mains gas"
        epc_record.hotwater_description = "From main system"
        epc_record.floor_description = "Floor Description"
        epc_record.floor_level = "Ground"
        epc_record.property_type = "House"
        inst1 = Property(0, postcode="AB12CD", address="Test Address", epc_record=epc_record)
        assert inst1.epc_record.uprn == 1
        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.epc_record.uprn == 1

    def test_set_features(
        self, property_instance, mock_cleaner, kwh_client,
    ):
        kwh_predictions = {
            "heating_kwh_predictions": pd.DataFrame(
                [
                    {"id": property_instance.epc_record.uprn, "predictions": 12000}
                ]
            ),
            "hotwater_kwh_predictions": pd.DataFrame(
                [
                    {"id": property_instance.epc_record.uprn, "predictions": 3000}
                ]
            ),
        }
        # Ensure required energy and walls attributes are set
        property_instance.energy["epc_co2_emissions"] = 1.0
        property_instance.energy["appliances_co2_emissions"] = 1.0
        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.set_features(
            mock_cleaner.cleaned,
            kwh_client,
            kwh_predictions
        )
        # ...existing code for assertions...

    def test_get_components_without_cleaned_data(self, property_instance, mock_cleaner):
        # Modify the mock EpcClean to not have cleaned data
        mock_cleaner.cleaned = {}
        # No direct assignment to prepared_epc here, but for robustness, patch if needed
        # 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.set_features(mock_cleaner.cleaned, pd.DataFrame(), pd.DataFrame())

    def test_get_components_no_attributes(
        self, property_instance, mock_cleaner, kwh_client
    ):
        kwh_predictions = {
            "heating_kwh_predictions": pd.DataFrame(
                [
                    {"id": property_instance.epc_record.uprn, "predictions": 12000}
                ]
            ),
            "hotwater_kwh_predictions": pd.DataFrame(
                [
                    {"id": property_instance.epc_record.uprn, "predictions": 3000}
                ]
            ),
        }
        # Modify the mock cleaner to have no attributes for a specific description
        mock_cleaner.cleaned = {
            "roof-description": []
        }
        property_instance.epc_record.roof_description = "Pitched, no insulation"
        # Ensure required energy and walls attributes are set
        property_instance.energy["epc_co2_emissions"] = 1.0
        property_instance.energy["appliances_co2_emissions"] = 1.0
        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,
        }
        # Ensure required floor attribute is set
        property_instance.floor = {
            "original_description": "Solid, no insulation (assumed)",
            "clean_description": "Pitched, 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": True,
            "another_property_below": False,
            "insulation_thickness": "none",
            "floor_thermal_transmittance": None,
            "floor_insulation_thickness": "none"
        }
        property_instance.set_features(
            mock_cleaner.cleaned,
            kwh_client,
            kwh_predictions
        )

        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, kwh_client
    ):
        # This shouldn't happen - it would mean a cleaning error
        property_instance.epc_record.roof_description = "Roof Description"
        cleaned = {
            "roof-description": [
                {"original_description": "Roof Description"},
                {"original_description": "Roof Description"}
            ]
        }
        kwh_predictions = {
            "heating_kwh_predictions": pd.DataFrame(
                [
                    {"id": property_instance.epc_record.uprn, "predictions": 12000}
                ]
            ),
            "hotwater_kwh_predictions": pd.DataFrame(
                [
                    {"id": property_instance.epc_record.uprn, "predictions": 3000}
                ]
            ),
        }
        # 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.set_features(cleaned, kwh_client, kwh_predictions)

    def test_set_spatial(self):
        from unittest.mock import patch, PropertyMock
        epc_record = EPCRecord()
        with patch.object(type(epc_record), "prepared_epc", new_callable=PropertyMock) as mock_prepared_epc:
            mock_prepared_epc.return_value = mock_epc_response["rows"][0]
            epc_record.uprn = int(mock_epc_response["rows"][0]["uprn"])
            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):
        # 1st case: floor-level '01', property-type 'Flat'
        epc_record = EPCRecord()
        epc_record.floor_level = '01'
        epc_record.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

        # 2nd case: floor-level 'Ground', property-type 'Flat'
        epc_record = EPCRecord()
        epc_record.floor_level = 'Ground'
        epc_record.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

        # 3rd case: floor-level '02', property-type 'Flat'
        epc_record = EPCRecord()
        epc_record.floor_level = '02'
        epc_record.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

        # 4th case: floor-level '', property-type 'House'
        epc_record = EPCRecord()
        epc_record.floor_level = ''
        epc_record.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
