from typing import Dict, List, Tuple

import pandas as pd
import requests
from dash import dash_table as dt

from codecarbon.core.emissions import Emissions
from codecarbon.input import DataSource, DataSourceException


class Data:
    def __init__(self):
        self._data_source = DataSource()
        self._emissions = Emissions(self._data_source)

    @staticmethod
    def get_project_data(df: pd.DataFrame, project_name) -> dt.DataTable:
        project_df = df[df.project_name == project_name]
        project_df = project_df.sort_values(by="timestamp")
        project_data = project_df.to_dict("rows")
        columns = [{"name": column, "id": column} for column in project_df.columns]
        return dt.DataTable(data=project_data, columns=columns)

    @staticmethod
    def get_project_summary(project_data: List[Dict]):
        last_run = project_data[-1]
        project_summary = {
            "last_run": {
                "timestamp": last_run["timestamp"],
                "duration": last_run["duration"],
                "emissions": round(last_run["emissions"], 1),
                "energy_consumed": round((last_run["energy_consumed"]), 1),
            },
            "total": {
                "duration": sum(
                    map(lambda experiment: experiment["duration"], project_data)
                ),
                "emissions": sum(
                    map(lambda experiment: experiment["emissions"], project_data)
                ),
                "energy_consumed": sum(
                    map(lambda experiment: experiment["energy_consumed"], project_data)
                ),
            },
            "country_name": last_run["country_name"],
            "country_iso_code": last_run["country_iso_code"],
            "region": last_run["region"],
            "on_cloud": last_run["on_cloud"],
            "cloud_provider": last_run["cloud_provider"],
            "cloud_region": last_run["cloud_region"],
        }
        return project_summary

    def get_car_miles(self, project_carbon_equivalent: float):
        """
        8.89 × 10-3 metric tons CO2/gallon gasoline ×
        1/22.0 miles per gallon car/truck average ×
        1 CO2, CH4, and N2O/0.988 CO2
        = 4.09 x 10-4 metric tons CO2E/mile
        = 0.409 kg CO2E/mile
        Source: EPA
        :param project_carbon_equivalent: total project emissions in kg CO2E
        :return: number of miles driven by avg car
        """
        return f"{project_carbon_equivalent / 0.409:.0f}"

    def get_tv_time(self, project_carbon_equivalent: float):
        """
        Gives the amount of time
        a 32-inch LCD flat screen TV will emit
        an equivalent amount of carbon
        Ratio is 0.097 kg CO2 / 1 hour tv
        :param project_carbon_equivalent: total project emissions in kg CO2E
        :return: equivalent TV time
        """
        time_in_minutes = project_carbon_equivalent * (1 / 0.097) * 60
        formated_value = f"{time_in_minutes:.0f} minutes"
        if time_in_minutes >= 60:
            time_in_hours = time_in_minutes / 60
            formated_value = f"{time_in_hours:.0f} hours"
            if time_in_hours >= 24:
                time_in_days = time_in_hours / 24
                formated_value = f"{time_in_days:.0f} days"
        return formated_value

    def get_household_fraction(self, project_carbon_equivalent: float):
        """
        Total CO2 emissions for energy use per home: 5.734 metric tons CO2 for electricity
        + 2.06 metric tons CO2 for natural gas + 0.26 metric tons CO2 for liquid petroleum gas
         + 0.30 metric tons CO2 for fuel oil  = 8.35 metric tons CO2 per home per year / 52 weeks
         = 160.58 kg CO2/week on average
        Source: EPA
        :param project_carbon_equivalent: total project emissions in kg CO2E
        :return: % of weekly emissions re: an average American household
        """
        return f"{project_carbon_equivalent / 160.58 * 100:.2f}"

    def get_global_emissions_choropleth_data(
        self, net_energy_consumed: float
    ) -> List[Dict]:
        def formatted_energy_percentage(energy_type: float, total: float) -> float:
            return float(f"{energy_type / total * 100:.1f}")

        global_energy_mix = self._data_source.get_global_energy_mix_data()
        choropleth_data = []
        for country_iso_code in global_energy_mix.keys():
            country_name = global_energy_mix[country_iso_code]["country_name"]

            if country_iso_code not in ["_define", "ATA"]:
                from codecarbon.core.units import Energy

                energy_consumed = Energy.from_energy(kWh=net_energy_consumed)

                from codecarbon.external.geography import GeoMetadata

                country_emissions = self._emissions.get_country_emissions(
                    energy_consumed,
                    GeoMetadata(
                        country_name=country_name, country_iso_code=country_iso_code
                    ),
                )
                total = global_energy_mix[country_iso_code]["total_TWh"]
                choropleth_data.append(
                    {
                        "iso_code": country_iso_code,
                        "emissions": country_emissions,
                        "country": country_name,
                        "fossil": formatted_energy_percentage(
                            global_energy_mix[country_iso_code]["fossil_TWh"], total
                        ),
                        "geothermal": formatted_energy_percentage(
                            global_energy_mix[country_iso_code]["geothermal_TWh"], total
                        ),
                        "hydroelectricity": formatted_energy_percentage(
                            global_energy_mix[country_iso_code]["hydroelectricity_TWh"],
                            total,
                        ),
                        "nuclear": formatted_energy_percentage(
                            global_energy_mix[country_iso_code]["nuclear_TWh"], total
                        ),
                        "solar": formatted_energy_percentage(
                            global_energy_mix[country_iso_code]["solar_TWh"], total
                        ),
                        "wind": formatted_energy_percentage(
                            global_energy_mix[country_iso_code]["wind_TWh"], total
                        ),
                    }
                )
        return choropleth_data

    def get_regional_emissions_choropleth_data(
        self, net_energy_consumed: float, country_iso_code: str
    ) -> List[Dict]:
        # add country codes here to render for different countries
        if country_iso_code.upper() not in ["USA", "CAN"]:
            return [{"region_code": "", "region_name": "", "emissions": ""}]

        try:
            region_emissions = self._data_source.get_country_emissions_data(
                country_iso_code.lower()
            )
        except (
            DataSourceException
        ):  # This country has regional data at the energy mix level, not the emissions level
            country_energy_mix = self._data_source.get_country_energy_mix_data(
                country_iso_code.lower()
            )
            region_emissions = {
                region: {"regionCode": region}
                for region, energy_mix in country_energy_mix.items()
            }
        choropleth_data = []
        for region_name in region_emissions.keys():
            region_code = region_emissions[region_name]["regionCode"]
            if region_name not in ["_unit"]:
                from codecarbon.core.units import Energy

                energy_consumed = Energy.from_energy(kWh=net_energy_consumed)

                from codecarbon.external.geography import GeoMetadata

                emissions = self._emissions.get_region_emissions(
                    energy_consumed,
                    GeoMetadata(country_iso_code=country_iso_code, region=region_name),
                )

                choropleth_data.append(
                    {
                        "region_code": region_code,
                        "region_name": region_name.upper(),
                        "emissions": emissions,
                    }
                )
        return choropleth_data

    def get_cloud_emissions_barchart_data(
        self,
        net_energy_consumed: float,
        on_cloud: str,
        cloud_provider: str,
        cloud_region: str,
    ) -> Tuple[str, pd.DataFrame]:
        if on_cloud == "N":
            return (
                "",
                pd.DataFrame(data={"region": [], "emissions": [], "country_name": []}),
            )
        cloud_emissions = self._data_source.get_cloud_emissions_data()
        cloud_emissions = cloud_emissions[
            ["provider", "providerName", "region", "impact", "country_name"]
        ]

        from codecarbon.core.units import EmissionsPerKWh

        cloud_emissions["emissions"] = cloud_emissions.apply(
            lambda row: EmissionsPerKWh.from_g_per_kWh(row.impact).kgs_per_kWh
            * net_energy_consumed,
            axis=1,
        )

        cloud_emissions_project_region = cloud_emissions[
            cloud_emissions.region == cloud_region
        ]
        cloud_emissions = cloud_emissions[
            (cloud_emissions.provider == cloud_provider)
            & (cloud_emissions.region != cloud_region)
        ].sort_values(by="emissions")

        return (
            cloud_emissions_project_region.iloc[0, :].providerName,
            pd.concat([cloud_emissions_project_region, cloud_emissions]),
        )

    @staticmethod
    def get_data_from_api(host):
        transformed_projects = []
        project_list = Data.list_projects(host)
        for project in project_list:
            project_sum_by_experiments_url = (
                host + f"/experiments/{project['id']}/detailed_sums"
            )
            project_name = project["name"]
            sums = requests.get(project_sum_by_experiments_url).json()
            for experiment in sums:
                experiment["project_name"] = project_name
                # experiment["emission_rate"] = 0
                # if experiment["emissions_count"] > 0:
                #     experiment["emission_rate"] = (
                #         experiment["emissions_rate"] / experiment["emissions_count"]
                #     )
                transformed_projects.append(experiment)
        df_projects = pd.DataFrame(transformed_projects)
        return df_projects

    @staticmethod
    def list_projects(host):
        projects = []
        teams_url = host + "/teams"
        teams = requests.get(teams_url).json()
        for team in teams:
            projets_url = host + f"/projects/team/{team['id']}"
            team_projects = requests.get(projets_url).json()
            if team_projects:
                projects.append(
                    list(
                        map(
                            lambda x: {"id": x["id"], "name": x["name"]},
                            iter(team_projects),
                        )
                    )
                )
        project_list = sum(projects, [])
        return project_list
