import json
from typing import Optional, Any, Mapping, Dict, Union, List

import pandas as pd
from sqlalchemy.orm import Session

from backend.export.property_scenarios.input_schema import ExportRequest
from backend.export.property_scenarios.db_functions import DbMethods
from backend.app.db.connection import db_read_session
from backend.app.utils import sap_to_epc
from utils.logger import setup_logger

logger = setup_logger()


def choose_group_keys(payload: ExportRequest) -> List[Union[int, str]]:
    if payload.default_plans_only:
        return ["default_plans"]  # Single export, no scenario grouping
    return payload.scenario_ids


def has_solar_with_battery(materials_list: Optional[List[Dict[str, Any]]]) -> bool:
    """
    Simple check to determine if any material in the list is a solar PV measure that includes a battery.
    :param materials_list:
    :return:
    """
    for m in materials_list or []:
        if m.get("type") == "solar_pv" and m.get("includes_battery") is True:
            return True
    return False


def process_export(
    payload: ExportRequest, session: Session
) -> Dict[Union[str, int], pd.DataFrame]:
    export_files: Dict[Union[str, int], pd.DataFrame] = {}

    db_methods = DbMethods(session)

    properties_df = db_methods.get_properties(payload.portfolio_id)

    logger.info("Retrieved %s properties for export", len(properties_df))

    plans_df: pd.DataFrame = db_methods.get_latest_plans(
        portfolio_id=payload.portfolio_id,
        scenario_ids=payload.scenario_ids,
        default_only=bool(payload.default_plans_only),
    )

    logger.info("Retrieved %s plans for export", len(plans_df))

    if plans_df.empty:
        logger.info(
            "Empty plans dataframe - no plans to export. Returning empty export."
        )
        return export_files
    plan_ids: List[int] = plans_df["id"].tolist()
    recommendations_df: pd.DataFrame = db_methods.get_recommendations(plan_ids)

    logger.info("Retrieved %s recommendations for export", len(recommendations_df))

    recommendations_df = db_methods.attach_materials(recommendations_df)

    recommendations_df["has_solar_with_battery"] = recommendations_df[
        "materials"
    ].apply(has_solar_with_battery)

    _filter = (recommendations_df["measure_type"] == "solar_pv") & (
        recommendations_df["has_solar_with_battery"]
    )

    recommendations_df.loc[_filter, "measure_type"] = (
        recommendations_df.loc[_filter, "measure_type"] + "_with_battery"
    )

    group_keys: List[Union[str, int]] = choose_group_keys(payload)

    for group_key in group_keys:

        if payload.default_plans_only:
            scenario_recs = recommendations_df
        else:
            scenario_recs = recommendations_df[
                recommendations_df["scenario_id"] == group_key
                ]

        if scenario_recs.empty:
            logger.info(
                "No recommendations found for group_key %s - skipping export for this group",
                group_key,
            )
            continue

        measures_df: pd.DataFrame = scenario_recs[
            ["property_id", "measure_type", "plan_name", "estimated_cost"]
        ].drop_duplicates()

        pivot: pd.DataFrame = measures_df.pivot(
            index=["property_id", "plan_name"],
            columns="measure_type",
            values="estimated_cost",
        ).reset_index()

        pivot["total_retrofit_cost"] = pivot.drop(
            columns=["property_id", "plan_name"]
        ).sum(axis=1)

        post_sap: pd.DataFrame = (
            scenario_recs.groupby("property_id")[["sap_points"]].sum().reset_index()
        )

        df: pd.DataFrame = (
            properties_df.rename(columns={"solar_pv": "existing_solar_pv"})
            .merge(pivot, how="left", on="property_id")
            .merge(post_sap, how="left", on="property_id")
        )

        df["sap_points"] = df["sap_points"].fillna(0)
        df["predicted_post_works_sap"] = df["current_sap_points"] + df["sap_points"]
        df["predicted_post_works_epc"] = df["predicted_post_works_sap"].apply(
            sap_to_epc
        )

        export_files[group_key] = df

    return export_files


# ============================================================
# Lambda Handler
# ============================================================


def handler(
    event: Mapping[str, Any], context: Optional[Any]
) -> Mapping[str, Union[int, str]]:
    """
    Example event:
    body_dict = {
        "task_id": "test",
        "subtask_id": "test",
        "portfolio_id": 632,
        "scenario_ids": [1144],
        "default_plans_only": False,
    }
    :param event: Lambda event containing export request details
    :param context: Lambda context (not used in this handler but included for completeness)
    :return: HTTP response indicating success or failure of the export operation
    """
    for record in event.get("Records", []):
        try:
            body_dict = json.loads(record["body"])

            logger.debug("Validating request body")
            payload = ExportRequest.model_validate(body_dict)

            if payload.scenario_ids_ignored:
                logger.warning(
                    "Received scenario_ids in request body but they will be ignored "
                    "because default_plans_only is set to True"
                )

            logger.debug("Successfully validated request body")
            with db_read_session() as session:
                exported_files = process_export(payload, session)

            # TODO: Need to handle the exported files - e.g. upload to s3 and email a presigned url
            output_path = f"/tmp/export_{payload.portfolio_id}.xlsx"
            with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
                for group_key, df in exported_files.items():
                    sheet_name = str(group_key)[:31]  # Excel sheet names max 31 chars
                    df.to_excel(writer, sheet_name=sheet_name, index=False)
            logger.info("Exported files written to %s", output_path)
            return {
                "statusCode": 200,
                "body": json.dumps({}),
            }

        except Exception as e:
            logger.error(f"Failed to process record: {e}")
            return {
                "statusCode": 500,
                "body": json.dumps({"message": "Failed to process export request"}),
            }

    return {
        "statusCode": 201,
        "body": json.dumps({"message": "No records to process"}),
    }
