from typing import Any, Dict, Optional, Tuple
from datetime import date

from backend.condition.domain.aspect_condition import AspectCondition
from backend.condition.domain.element import Element
from backend.condition.domain.element_type import ElementType
from backend.condition.domain.mapping.element_mapping import ElementMapping
from backend.condition.domain.mapping.lbwf.lbwf_element_map import LBWF_ELEMENT_MAP
from backend.condition.domain.mapping.mapper import Mapper
from backend.condition.domain.property_condition_survey import PropertyConditionSurvey
from backend.condition.parsing.records.lbwf.lbwf_asset_condition import (
    LbwfAssetCondition,
)
from backend.condition.parsing.records.lbwf.lbwf_house import LbwfHouse
from utils.logger import setup_logger

logger = setup_logger()


class LbwfMapper(Mapper):

    def map_asset_conditions_for_property(
        self, client_property_data: Any, survey_year: Optional[int] = None
    ) -> PropertyConditionSurvey:
        assert isinstance(
            client_property_data, LbwfHouse
        )  # TODO: think of a better way to do this

        elements_by_key: dict[tuple[ElementType, int], Element] = {}

        for raw_asset in client_property_data.assets:
            if raw_asset.element_code in ["DECNTHMINC", "EICINSFREQ"]:
                # skip metadata rows
                continue

            element_mapping = LbwfMapper._safe_map_element(raw_asset)

            if not element_mapping:
                continue

            aspect_condition = LbwfMapper._build_aspect_condition(
                raw_asset, element_mapping, survey_year
            )

            element_key = (
                element_mapping.elementType,
                element_mapping.element_instance or 1,
            )

            LbwfMapper._attach_aspect_condition_to_element(
                elements_by_key, element_key, aspect_condition
            )

        return PropertyConditionSurvey(
            uprn=client_property_data.uprn,
            elements=list(elements_by_key.values()),
            date=date(2000, 1, 1),  # Temp - not sure how to get this
            source="LBWF",  # TODO: Make this the system, not the client
        )

    @staticmethod
    def _safe_map_element(raw_asset: LbwfAssetCondition) -> Optional[ElementMapping]:
        try:
            return LbwfMapper._map_element(raw_asset.element_code)
        except KeyError:
            logger.warning(
                logger.warning(
                    f"Unrecognised LBWF Asset Element: "
                    f"{raw_asset.element_code} ({raw_asset.element_code_description})). "
                    "Skipping record"
                )
            )
            return None

    @staticmethod
    def _map_element(lbwf_element_code: str) -> ElementMapping:
        return LBWF_ELEMENT_MAP[lbwf_element_code]

    @staticmethod
    def _build_aspect_condition(
        raw_asset, element_mapping: ElementMapping, survey_year: int
    ) -> AspectCondition:
        return AspectCondition(
            aspect_type=element_mapping.aspect_type,
            aspect_instance=element_mapping.aspect_instance or 1,
            value=raw_asset.attribute_code_description,
            quantity=raw_asset.quantity,
            install_date=raw_asset.install_date,
            renewal_year=LbwfMapper._calculate_renewal_year(raw_asset, survey_year),
            comments=raw_asset.element_comments,
        )

    @staticmethod
    def _attach_aspect_condition_to_element(
        elements_by_key: Dict[Tuple[ElementType, int], Element],
        element_key: Tuple[ElementType, int],
        aspect_condition: AspectCondition,
    ) -> None:
        element = elements_by_key.get(element_key)

        if element is None:
            element = Element(
                element_type=element_key[0],
                element_instance=element_key[1],
                aspect_conditions=[],
            )
            elements_by_key[element_key] = element

        element.aspect_conditions.append(aspect_condition)

    @staticmethod
    def _calculate_renewal_year(
        lbwf_asset: LbwfAssetCondition, survey_year: Optional[int]
    ) -> Optional[int]:
        remaining_life_years: Optional[int] = lbwf_asset.remaining_life
        if not remaining_life_years:
            return None

        if not survey_year:
            return None

        try:
            return survey_year + remaining_life_years
        except:
            logger.debug(
                f"Unable to map LBWF Asset remaining life {remaining_life_years} to renewal year, returning None"
            )
            return None
