from dataclasses import dataclass
from datetime import date
from typing import List, Optional, Union

from datatypes.epc.domain.epc import Epc


@dataclass
class EnergyElement:
    description: str
    energy_efficiency_rating: int
    environmental_efficiency_rating: int


@dataclass
class InstantaneousWwhrs:
    wwhrs_index_number1: Optional[int] = None
    wwhrs_index_number2: Optional[int] = None


@dataclass
class MainHeatingDetail:
    has_fghrs: bool
    main_fuel_type: Union[int, str]  # int from API, str from site notes
    heat_emitter_type: Union[int, str]  # int from API, str from site notes
    emitter_temperature: Union[int, str]
    main_heating_control: Union[int, str]  # int from API, str from site notes
    fan_flue_present: Optional[bool] = None
    boiler_flue_type: Optional[int] = None  # TODO: make enum?
    boiler_ignition_type: Optional[int] = None  # TODO: make enum?
    central_heating_pump_age: Optional[int] = None
    central_heating_pump_age_str: Optional[str] = None  # str from site notes e.g. "Unknown", "Pre 2013"
    main_heating_index_number: Optional[int] = None
    sap_main_heating_code: Optional[int] = None  # TODO: make enum?
    main_heating_number: Optional[int] = None
    main_heating_category: Optional[int] = None
    main_heating_fraction: Optional[int] = None
    main_heating_data_source: Optional[int] = None
    condensing: Optional[bool] = None
    weather_compensator: Optional[bool] = None


@dataclass
class ShowerOutlet:
    shower_outlet_type: Union[int, str]
    shower_wwhrs: Optional[int] = None


@dataclass
class ShowerOutlets:
    # TODO: consolidate ShowerOutlet and ShowerOutlets
    shower_outlet: ShowerOutlet


@dataclass
class SapHeating:
    instantaneous_wwhrs: InstantaneousWwhrs
    main_heating_details: List[MainHeatingDetail]
    has_fixed_air_conditioning: bool
    cylinder_size: Optional[Union[int, str]] = (
        None  # int code from API; str (e.g. "Normal (90-130 litres)") from site notes
    )
    water_heating_code: Optional[int] = None  # TODO: make enum?
    water_heating_fuel: Optional[int] = None  # TODO: make enum?
    immersion_heating_type: Optional[Union[int, str]] = None  # TODO: make enum?
    shower_outlets: Optional[ShowerOutlets] = None
    cylinder_insulation_type: Optional[Union[int, str]] = None
    cylinder_thermostat: Optional[str] = None
    secondary_fuel_type: Optional[int] = None
    secondary_heating_type: Optional[Union[int, str]] = None  # int from API; str from site notes
    cylinder_insulation_thickness_mm: Optional[int] = None


@dataclass
class SapVentilation:
    ventilation_type: Optional[str] = None
    draught_lobby: Optional[bool] = None
    pressure_test: Optional[str] = None  # str from site notes e.g. "No test"; int in API via mechanical_ventilation
    open_flues_count: Optional[int] = None
    closed_flues_count: Optional[int] = None
    boiler_flues_count: Optional[int] = None
    other_flues_count: Optional[int] = None
    extract_fans_count: Optional[int] = None
    passive_vents_count: Optional[int] = None
    flueless_gas_fires_count: Optional[int] = None
    ventilation_in_pcdf_database: Optional[bool] = None


@dataclass
class WindowTransmissionDetails:
    u_value: float
    data_source: Union[int, str]
    solar_transmittance: float


@dataclass
class SapWindow:
    frame_material: Optional[str]
    glazing_gap: Union[int, str]
    orientation: Union[int, str]
    window_type: Union[int, str]
    glazing_type: Union[int, str]
    window_width: float
    window_height: float
    draught_proofed: Union[bool, str]  # TODO: make enum/mapping?
    window_location: Union[int, str]  # TODO: make enum/mapping
    window_wall_type: Union[int, str]  # TODO: make enum/mapping
    permanent_shutters_present: Union[bool, str]  # TODO: make enum/mapping
    frame_factor: Optional[float] = None
    window_transmission_details: Optional[WindowTransmissionDetails] = None
    permanent_shutters_insulated: Optional[str] = None


@dataclass
class PvBattery:
    battery_capacity: float


@dataclass
class PvBatteries:
    pv_battery: PvBattery


@dataclass
class WindTurbineDetails:
    hub_height: float
    rotor_diameter: float


@dataclass
class PhotovoltaicSupplyNoneOrNoDetails:
    percent_roof_area: int


@dataclass
class PhotovoltaicSupply:
    none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails


@dataclass
class SapEnergySource:
    mains_gas: bool
    meter_type: str  # int in API, str (e.g. "Single") in site notes
    pv_battery_count: int
    wind_turbines_count: int
    gas_smart_meter_present: bool
    is_dwelling_export_capable: bool
    wind_turbines_terrain_type: str  # int in API, str (e.g. "Suburban") in site notes
    electricity_smart_meter_present: bool

    pv_connection: Optional[Union[int, str]] = None  # int from API; str from site notes
    photovoltaic_supply: Optional[PhotovoltaicSupply] = None
    wind_turbine_details: Optional[WindTurbineDetails] = None
    pv_batteries: Optional[PvBatteries] = None


@dataclass
class SapFloorDimension:
    room_height_m: float
    total_floor_area_m2: float
    party_wall_length_m: float
    heat_loss_perimeter_m: float

    floor: Optional[int] = None
    floor_insulation: Optional[int] = None
    floor_construction: Optional[int] = None


@dataclass
class SapRoomInRoof:
    floor_area: Union[int, float]
    construction_age_band: str


@dataclass
class SapAlternativeWall:
    wall_area: float
    wall_dry_lined: str
    wall_construction: int
    wall_insulation_type: int
    wall_thickness_measured: str
    wall_insulation_thickness: Optional[str] = None


@dataclass
class SapBuildingPart:
    # General
    identifier: str  # e.g. "main", "roof"
    construction_age_band: str

    # Wall
    wall_construction: Union[
        int, str
    ]  # int from API, str from site notes TODO: make enum/mapping?
    wall_insulation_type: Union[
        int, str
    ]  # int from API, str from site notes TODO: make enum/mapping?
    wall_thickness_measured: bool
    party_wall_construction: Union[int, str]  # TODO: make enum/mapping?

    # Floor
    sap_floor_dimensions: List[
        SapFloorDimension
    ]  # Not included in site notes; should this be optional?

    # Optional
    building_part_number: Optional[int] = (
        None  # Not sure how we get this from site notes
    )
    wall_dry_lined: Optional[bool] = None  # Don't think we have this in site notes
    wall_thickness_mm: Optional[int] = None
    wall_insulation_thickness: Optional[str] = None
    sap_alternative_wall_1: Optional[SapAlternativeWall] = None
    sap_alternative_wall_2: Optional[SapAlternativeWall] = None

    floor_heat_loss: Optional[int] = None
    floor_insulation_thickness: Optional[str] = None
    flat_roof_insulation_thickness: Optional[Union[str, int]] = (
        None  # TODO: make enum/mapping?
    )
    floor_type: Optional[str] = None  # str from site notes e.g. "Ground Floor"
    floor_construction_type: Optional[str] = None  # str from site notes; distinct from floor_construction: int in SapFloorDimension
    floor_insulation_type_str: Optional[str] = None  # str from site notes e.g. "As Built"
    floor_u_value_known: Optional[bool] = None

    roof_construction: Optional[int] = None
    roof_insulation_location: Optional[Union[int, str]] = (
        None  # TODO: make enum/mapping?
    )
    roof_insulation_thickness: Optional[Union[str, int]] = (
        None  # TODO: make enum/mapping?
    )
    sap_room_in_roof: Optional[SapRoomInRoof] = None


@dataclass
class WindowsTransmissionDetails:
    u_value: float
    data_source: int
    solar_transmittance: float


@dataclass
class SapFlatDetails:
    level: int
    top_storey: str
    flat_location: int
    heat_loss_corridor: int
    storey_count: Optional[int] = None
    unheated_corridor_length_m: Optional[int] = None


@dataclass
class EpcPropertyData:
    # General
    dwelling_type: str  # TODO: make enum?
    inspection_date: date
    tenure: str  # str in site notes; stringified int (e.g. "1") from API
    transaction_type: str  # str in site notes; stringified int from API
    address_line_1: str
    postcode: str
    post_town: str

    # Elements
    roofs: List[EnergyElement]
    walls: List[EnergyElement]
    floors: List[EnergyElement]
    main_heating: List[EnergyElement]
    door_count: int
    sap_heating: SapHeating
    sap_windows: List[SapWindow]
    sap_energy_source: SapEnergySource
    sap_building_parts: List[SapBuildingPart]
    solar_water_heating: bool
    has_hot_water_cylinder: bool  # must be inferred when mapping from site notes
    has_fixed_air_conditioning: bool

    # Counts
    wet_rooms_count: int  # If this isn't provided, should it be 0 or None?
    extensions_count: int  # If this isn't provided, should it be 0 or None?
    heated_rooms_count: int  # If this isn't provided, should it be 0 or None?
    open_chimneys_count: int
    habitable_rooms_count: int
    insulated_door_count: (
        int  # Called "number_of_insulated_external_doors" in site notes; same thing?
    )
    cfl_fixed_lighting_bulbs_count: int
    led_fixed_lighting_bulbs_count: int
    incandescent_fixed_lighting_bulbs_count: int

    # Measurements
    total_floor_area_m2: float

    # Optional fields
    assessment_type: Optional[str] = None  # not available from site notes
    sap_version: Optional[float] = None  # not available from site notes
    uprn: Optional[int] = None  # not available from site notes
    status: Optional[str] = None  # not available from site notes
    window: Optional[EnergyElement] = None  # not available from site notes
    lighting: Optional[EnergyElement] = None  # not available from site notes
    hot_water: Optional[EnergyElement] = None  # not available from site notes
    schema_type: Optional[str] = None
    schema_versions_original: Optional[str] = None
    report_type: Optional[str] = None  # TODO: make enum?
    report_reference: Optional[str] = None
    uprn_source: Optional[str] = None
    address_line_2: Optional[str] = None
    region_code: Optional[str] = None  # TODO: make enum?
    country_code: Optional[str] = None
    built_form: Optional[str] = None  # TODO: make enum?
    property_type: Optional[str] = None
    pressure_test: Optional[int] = None
    language_code: Optional[str] = None
    completion_date: Optional[date] = None
    registration_date: Optional[date] = None
    measurement_type: Optional[int] = None  # What is this?
    conservatory_type: Optional[int] = (
        None  # What is this? site notes have "has_conservatory" flag
    )
    has_conservatory: Optional[bool] = None  # mapped directly from site notes
    has_heated_separate_conservatory: Optional[bool] = None
    secondary_heating: Optional[EnergyElement] = (
        None  # For site notes, secondary_fuel maps to sap_heating.secondary_fuel_type
    )
    blocked_chimneys_count: Optional[int] = None
    energy_rating_average: Optional[int] = None
    main_heating_controls: Optional[EnergyElement] = (
        None  # site notes has heating_and_hot_water.main_heating.controls: str - doesn't map to EnergyElement
    )
    current_energy_efficiency_band: Optional[Epc] = None  # not available in site notes?
    environmental_impact_current: Optional[int] = None
    heating_cost_current: Optional[float] = None
    co2_emissions_current: Optional[float] = None
    energy_consumption_current: Optional[int] = None
    energy_rating_current: Optional[int] = None
    lighting_cost_current: Optional[float] = None
    hot_water_cost_current: Optional[float] = None
    insulated_door_u_value: Optional[float] = None  # Not available in site notes
    mechanical_ventilation: Optional[int] = (
        None  # ventilation details present in site notes, but I'm not sure they correspond directly to the integers returned by the API here
    )
    percent_draughtproofed: Optional[int] = (
        None  # Site notes have draught_proofed: bool field for each window, can we use that to infer percentage?
    )
    heating_cost_potential: Optional[float] = None
    co2_emissions_potential: Optional[float] = None
    energy_consumption_potential: Optional[int] = None
    energy_rating_potential: Optional[float] = None
    lighting_cost_potential: Optional[float] = None
    hot_water_cost_potential: Optional[float] = None
    environmental_impact_potential: Optional[int] = None
    potential_energy_efficiency_band: Optional[Epc] = (
        None  # not available in site notes
    )
    # renewable_heat_incentive: Optional[Any] = None # Not sure what this is, skip for now
    draughtproofed_door_count: Optional[int] = None
    mechanical_vent_duct_type: Optional[int] = None
    windows_transmission_details: Optional[WindowsTransmissionDetails] = None
    multiple_glazed_propertion: Optional[int] = None
    calculation_software_version: Optional[str] = None  # Do we care about this?
    mechanical_vent_duct_placement: Optional[int] = None
    mechanical_vent_duct_insulation: Optional[int] = None
    pressure_test_certificate_number: Optional[int] = None
    mechanical_ventilation_index_number: Optional[int] = None
    mechanical_vent_measured_installation: Optional[str] = None
    co2_emissions_current_per_floor_area: Optional[int] = None
    low_energy_fixed_lighting_bulbs_count: Optional[int] = None
    sap_flat_details: Optional[SapFlatDetails] = None
    # survey_addendum: Optional[Any] = None # not sure how to handle, skip for now
    fixed_lighting_outlets_count: Optional[int] = None
    low_energy_fixed_lighting_outlets_count: Optional[int] = None
    # Site-notes-only fields
    sap_ventilation: Optional[SapVentilation] = None
    number_of_storeys: Optional[int] = None
    any_unheated_rooms: Optional[bool] = None
    waste_water_heat_recovery: Optional[str] = None
    hydro: Optional[bool] = None
    photovoltaic_array: Optional[bool] = None
