from backend.Property import Property
from typing import List
from recommendations.FloorRecommendations import FloorRecommendations
from recommendations.WallRecommendations import WallRecommendations
from recommendations.RoofRecommendations import RoofRecommendations
from recommendations.VentilationRecommendations import VentilationRecommendations
from recommendations.FireplaceRecommendations import FireplaceRecommendations
from recommendations.LightingRecommendations import LightingRecommendations
from backend.ml_models.AnnualBillSavings import AnnualBillSavings


class Recommendations:
    """
    High level recommendations class, which sits above the measure specific recommendation classes
    """

    def __init__(
        self,
        property_instance: Property,
        materials: List
    ):
        """
        :param property_instance: Instance of the Property class, for the home associated to property_id
        :param materials: List of materials to be used in the recommendations
        """

        self.property_instance = property_instance
        self.materials = materials

        self.floor_recommender = FloorRecommendations(property_instance=property_instance, materials=materials)
        self.wall_recomender = WallRecommendations(property_instance=property_instance, materials=materials)
        self.roof_recommender = RoofRecommendations(property_instance=property_instance, materials=materials)
        self.ventilation_recomender = VentilationRecommendations(
            property_instance=property_instance, materials=materials
        )
        self.fireplace_recommender = FireplaceRecommendations(property_instance=property_instance)
        self.lighting_recommender = LightingRecommendations(property_instance=property_instance, materials=materials)

    def recommend(self):

        """
        This method runs the recommendations for the individual measures and then appends them to a list for output
        :return:
        """

        property_recommendations = []

        # Floor recommendations
        self.floor_recommender.recommend()
        if self.floor_recommender.recommendations:
            property_recommendations.append(self.floor_recommender.recommendations)

        # Wall recommendations
        self.wall_recomender.recommend()
        if self.wall_recomender.recommendations:
            property_recommendations.append(self.wall_recomender.recommendations)

        # Roof recommendations
        self.roof_recommender.recommend()
        if self.roof_recommender.recommendations:
            property_recommendations.append(self.roof_recommender.recommendations)

        # Ventilation recommendations
        self.ventilation_recomender.recommend()
        if self.ventilation_recomender.recommendation:
            property_recommendations.append(self.ventilation_recomender.recommendation)

        # Fireplace sealing recommendations
        self.fireplace_recommender.recommend()
        if self.fireplace_recommender.recommendation:
            property_recommendations.append(self.fireplace_recommender.recommendation)

        # Lighting recommendations
        self.lighting_recommender.recommend()
        if self.lighting_recommender.recommendation:
            property_recommendations.append(self.lighting_recommender.recommendation)

        # We insert temporary ids into the recommendations which is important for the optimiser later
        property_recommendations = self.insert_temp_recommendation_id(property_recommendations)

        return property_recommendations

    @staticmethod
    def insert_temp_recommendation_id(property_recommendations):
        """
        Creates a temporary recommendation id which is needed for
        filtering recommendations between default and no, after the optimiser has been
        run
        :param property_recommendations:  nested list of recommendations, grouped by data_types
        :return: Updated recommendations_to_upload, where where recommendation has a "recommendation_id"
                 integer inserted
        """
        idx = 0

        for recs in property_recommendations:
            for rec in recs:
                rec["recommendation_id"] = idx
                idx += 1

        return property_recommendations

    @classmethod
    def calculate_recommendation_impact(cls, property_instance, all_predictions, recommendations):

        """
        Given predictions from the model apis, with method will update the recommendations with the predicted
        impact of the recommendation on the property

        :param property_instance: Instance of the Property class, for the home associated to property_id
        :param all_predictions: dictionary of predictions from the model apis
        :param recommendations: dictionary of recommendations for the property
        :return:
        """

        property_sap_predictions = all_predictions["sap_change_predictions"][
            all_predictions["sap_change_predictions"]["property_id"] == str(property_instance.id)
            ]
        property_heat_predictions = all_predictions["heat_demand_predictions"][
            all_predictions["heat_demand_predictions"]["property_id"] == str(property_instance.id)
            ]
        property_carbon_predictions = all_predictions["carbon_change_predictions"][
            all_predictions["carbon_change_predictions"]["property_id"] == str(property_instance.id)
            ]

        property_recommendations = recommendations[property_instance.id].copy()

        for recommendations_by_type in property_recommendations:
            for rec in recommendations_by_type:

                new_heat_demand = property_heat_predictions[property_heat_predictions["recommendation_id"] == str(
                    rec["recommendation_id"]
                )]["predictions"].values[0]

                new_carbon = property_carbon_predictions[property_carbon_predictions["recommendation_id"] == str(
                    rec["recommendation_id"]
                )]["predictions"].values[0]

                # We don't use the model for low energy lighting at the moment
                if rec["type"] != "low_energy_lighting":
                    new_sap = property_sap_predictions[property_sap_predictions["recommendation_id"] == str(
                        rec["recommendation_id"]
                    )]["predictions"].values[0]
                    rec["sap_points"] = new_sap - float(property_instance.data["current-energy-efficiency"])

                if rec["type"] == "mechanical_ventilation":
                    # For the moment, we cap the number of SAP points that can be achieved by ventilation at 2
                    rec["sap_points"] = min(rec["sap_points"], VentilationRecommendations.SAP_LIMIT)

                rec["co2_equivalent_savings"] = float(property_instance.data["co2-emissions-current"]) - new_carbon

                # Energy consumption current is per meter squared, so we need to multiply by the floor area to get
                # an absolute figure for the home
                rec["heat_demand"] = (
                    (float(property_instance.data["energy-consumption-current"]) - new_heat_demand
                     ) * property_instance.floor_area)

                rec["energy_cost_savings"] = AnnualBillSavings.estimate(rec["heat_demand"])

                if (rec["sap_points"] is None) and (rec["co2_equivalent_savings"] is None) or (
                    rec["heat_demand"] is None) or (rec["energy_cost_savings"] is None):
                    raise ValueError("sap points, co2 or heat demand is missing")

        return property_recommendations
