"""
AirRecyclingAHU - Centrale de Traitement d'Air en mode recyclage.
"""

import logging
from typing import Dict, Optional
import pandas as pd

from AHU.air_humide import air_humide
from AHU.FreshAir.FreshAir import Object as FreshAir
from AHU.FreshAir.AirMix import Object as AirMix
from AHU.Coil.HeatingCoil import Object as HeatingCoil
from AHU.Coil.CoolingCoil_Expert import Object as CoolingCoil_Expert
from AHU.Humidification.Humidifier import Object as Humidifier
from AHU.Connect import Air_connect

logger = logging.getLogger(__name__)


class Object:
    """
    CTA en mode recyclage (melange air neuf + air recycle).

    Entrees:
        config (dict): configuration des composants
        data (pd.DataFrame): serie chronologique

    Sortie:
        df (pd.DataFrame): resultats de simulation (rempli apres calculate())
    """

    def __init__(self, config: Dict, data: pd.DataFrame):
        self.mode = "recycling"
        self.config = self._normalize_config(config)
        self.data = data
        self.df = None
        self.component_logs = {}
        logger.info("AirRecyclingAHU initialisee - %d lignes de donnees", len(data))
        logger.debug("Config: %s", self.config)

    @staticmethod
    def _normalize_config(config: Dict) -> Dict:
        defaults = {
            "recycling": False,
            "pre_heating_coil": False,
            "heating_coil": False,
            "cooling_coil": False,
            "humidifier": False,
            "humidifier_type": "adiabatique",
            "post_heating_coil": False
        }
        if config is None:
            raise ValueError("config est requis (dict).")

        normalized = defaults.copy()
        normalized.update(config)
        return normalized

    def _log_raw_air_point(self, key: str, air_obj, decimals: int = 2):
        if key not in self.component_logs:
            self.component_logs[key] = []

        self.component_logs[key].append({
            "T[°C]": round(air_obj.T, decimals),
            "RH[%]": round(air_obj.RH, decimals),
            "F_dry[kgas/s]": round(air_obj.F_dry, decimals),
            "h[kJ/kgda]": round(air_obj.h, decimals),
            "w[gH2O/kgda]": round(air_obj.w, decimals),
            "F_m3h[m3/h]": round(air_obj.F_dry / air_humide.Air_rho_hum(
                T_db=air_obj.T, RH=air_obj.RH) * 3600, decimals)
        })

    def _log_air_point(self, component_name: str, obj, Q_th: Optional[float] = None,
                       F_water: Optional[float] = None, decimals: int = 2):
        if component_name not in self.component_logs:
            self.component_logs[component_name] = []

        def extract_air_data(air_obj, label):
            return {
                f"{label}_T[°C]": round(air_obj.T, decimals),
                f"{label}_RH[%]": round(air_obj.RH, decimals),
                f"{label}_h[kJ/kgas]": round(air_obj.h, decimals),
                f"{label}_w[gH20/kgda]": round(air_obj.w, decimals),
                f"{label}_F[kg/s]": round(getattr(air_obj, "F", 0), decimals)
                if getattr(air_obj, "F", None) is not None else None,
                f"{label}_F_dry[kgas/s]": round(air_obj.F_dry, decimals),
                f"{label}F_m3h[m3/h]": round(air_obj.F_dry / air_humide.Air_rho_hum(
                    T_db=air_obj.T, RH=air_obj.RH) * 3600, decimals),
                f"{label}_P[Pa]": round(getattr(air_obj, "P", 0), decimals)
                if getattr(air_obj, "P", None) is not None else None,
                f"{label}_Pv_sat[Pa]": round(getattr(air_obj, "Pv_sat", 0), decimals)
                if getattr(air_obj, "Pv_sat", None) is not None else None
            }

        entry = {}

        if (hasattr(obj, "Inlet1") and hasattr(obj, "Outlet1") and
                hasattr(obj, "Inlet2") and hasattr(obj, "Outlet2")):
            entry.update(extract_air_data(obj.Inlet1, "Inlet1"))
            entry.update(extract_air_data(obj.Outlet1, "Outlet1"))
            entry.update(extract_air_data(obj.Inlet2, "Inlet2"))
            entry.update(extract_air_data(obj.Outlet2, "Outlet2"))

            if hasattr(obj, "heat_transfer1"):
                entry["Heat_recovery[kW]"] = round(obj.heat_transfer1, decimals)
            if hasattr(obj, "sensible_heat_transfer1"):
                entry["Sensible_heat1[kW]"] = round(obj.sensible_heat_transfer1, decimals)
            if hasattr(obj, "sensible_heat_transfer2"):
                entry["Sensible_heat2[kW]"] = round(obj.sensible_heat_transfer2, decimals)
            if hasattr(obj, "delta_mw1"):
                entry["Δmw1[gH2O/kgas]"] = round(obj.delta_mw1, decimals)
            if hasattr(obj, "delta_mw2"):
                entry["Δmw2[gH2O/kgas]"] = round(obj.delta_mw2, decimals)
            if hasattr(obj, "T_efficiency"):
                entry["T_efficiency[%]"] = round(obj.T_efficiency, decimals)
            if hasattr(obj, "h_efficiency"):
                entry["h_efficiency[%]"] = round(obj.h_efficiency, decimals)

        elif hasattr(obj, "Outlet"):
            entry.update(extract_air_data(obj.Outlet, "Outlet"))
            if Q_th is not None:
                entry["Q_th[kW]"] = round(Q_th, decimals)
            if F_water is not None:
                entry["F_water[kg/h]"] = round(F_water, decimals)

        self.component_logs[component_name].append(entry)

    def _compile_results(self) -> pd.DataFrame:
        merged_df = pd.DataFrame()

        for comp, logs in self.component_logs.items():
            if logs:
                df_comp = pd.DataFrame(logs)
                df_comp = df_comp.add_prefix(f"{comp}_")
                merged_df = pd.concat([merged_df, df_comp], axis=1)

        if self.data is not None and "Timestamp" in self.data.columns:
            merged_df.insert(0, "Timestamp", self.data["Timestamp"])

        return merged_df

    def calculate(self, progress_callback=None):
        """
        Lance la simulation en mode recyclage.
        Stocke les resultats dans self.df (pd.DataFrame).
        """
        if self.data is None:
            raise ValueError("Aucune donnee chargee. Fournissez data.")

        self.component_logs = {
            "FA": [], "RA": [], "AMX": [], "MXA": [],
            "PREHC": [], "HC": [], "CC": [], "HMD": [], "POSTHC": []
        }

        total = len(self.data)
        logger.info("Demarrage simulation RECYCLAGE - %d points", total)

        for index, row in self.data.iterrows():
            try:
                total_flow = row['Supply Air flow [m3/h]']
                recyc_pct = row['Mix Recycled Air [%]']
                T_consigne = row['Supply Air Set Point [T°C]']
                w_target = air_humide.Air_w(T_db=T_consigne,
                                            RH=row['Supply Air Set Point [HR %]'])

                FA = FreshAir()
                FA.T = row['Fresh Air [T°C]']
                FA.RH = row['Fresh Air [HR %]']
                FA.F_m3h = total_flow * (1 - recyc_pct / 100)
                FA.calculate()
                self._log_raw_air_point("FA", FA)
                logger.debug("Ligne %d - FA: T=%.1f RH=%.0f%%", index + 1, FA.T, FA.RH)

                if self.config["recycling"] and recyc_pct > 0:
                    RA = FreshAir()
                    RA.T = row['Recycled Air [T°C]']
                    RA.RH = row['Recycled Air [HR %]']
                    RA.F_m3h = total_flow * (recyc_pct / 100)
                    RA.calculate()
                    self._log_raw_air_point("RA", RA)

                    AMX = AirMix()
                    Air_connect(AMX.Inlet1, FA.Outlet)
                    Air_connect(AMX.Inlet2, RA.Outlet)
                    AMX.calculate()
                    self._log_air_point("AMX", AMX)

                    MXA = FreshAir()
                    Air_connect(MXA.Inlet, AMX.Outlet)
                    MXA.T = air_humide.Air_T_db(h=AMX.Outlet.h, w=AMX.Outlet.w)
                    MXA.RH = air_humide.Air_RH(h=AMX.Outlet.h, w=AMX.Outlet.w)
                    MXA.F_m3h = total_flow
                    MXA.calculate()
                else:
                    MXA = FreshAir()
                    MXA.T = FA.T
                    MXA.RH = FA.RH
                    MXA.F_m3h = total_flow
                    MXA.calculate()

                self._log_air_point("MXA", MXA)
                current_air = MXA

                if self.config["pre_heating_coil"]:
                    PREHC = HeatingCoil()
                    PREHC.To_target = row.get('Defrost Coil Set Point [T°C]', None)
                    Air_connect(PREHC.Inlet, current_air.Outlet)
                    PREHC.calculate()
                    self._log_air_point("PREHC", PREHC, Q_th=PREHC.Q_th)
                    current_air = PREHC
                    logger.debug("Ligne %d - PREHC: Q_th=%.2f kW", index + 1, PREHC.Q_th)

                if self.config["heating_coil"]:
                    HC = HeatingCoil()
                    HC.To_target = T_consigne
                    Air_connect(HC.Inlet, current_air.Outlet)
                    HC.calculate()
                    self._log_air_point("HC", HC, Q_th=HC.Q_th)
                    current_air = HC
                    logger.debug("Ligne %d - HC: Q_th=%.2f kW", index + 1, HC.Q_th)

                if self.config["cooling_coil"]:
                    CC = CoolingCoil_Expert()
                    CC.T_target = T_consigne
                    CC.w_target = w_target
                    Air_connect(CC.Inlet, current_air.Outlet)
                    CC.calculate()
                    self._log_air_point("CC", CC, Q_th=CC.Q_th)
                    current_air = CC
                    logger.debug("Ligne %d - CC: Q_th=%.2f kW", index + 1, CC.Q_th)

                if self.config["humidifier"]:
                    HMD = Humidifier()
                    HMD.wo_target = w_target
                    HMD.HumidType = self.config["humidifier_type"]
                    Air_connect(HMD.Inlet, current_air.Outlet)
                    HMD.calculate()
                    self._log_air_point("HMD", HMD, Q_th=HMD.Q_th, F_water=HMD.F_water)
                    current_air = HMD
                    logger.debug("Ligne %d - HMD: Q_th=%.2f kW, F_water=%.2f kg/h",
                                 index + 1, HMD.Q_th, HMD.F_water)

                if self.config["post_heating_coil"]:
                    POSTHC = HeatingCoil()
                    POSTHC.To_target = T_consigne
                    Air_connect(POSTHC.Inlet, current_air.Outlet)
                    POSTHC.calculate()
                    self._log_air_point("POSTHC", POSTHC, Q_th=POSTHC.Q_th)
                    current_air = POSTHC
                    logger.debug("Ligne %d - POSTHC: Q_th=%.2f kW", index + 1, POSTHC.Q_th)

                if progress_callback:
                    progress_callback(index + 1, total)

            except Exception as e:
                logger.error("Erreur ligne %d : %s", index + 1, e, exc_info=True)
                continue

        self.df = self._compile_results()
        logger.info("Simulation RECYCLAGE terminee - %d lignes, %d colonnes",
                     len(self.df), self.df.shape[1])
