import enum
from typing import Iterable, List, NamedTuple, Optional, Type
from sqlalchemy import (
    Column,
    BigInteger,
    String,
    Float,
    Boolean,
    TIMESTAMP,
    ForeignKey,
    Enum,
)
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func
from datetime import datetime

from backend.app.db.base import Base
from backend.app.db.models.portfolio import Portfolio, PortfolioGoal, PropertyModel
from backend.app.db.models.materials import Material
from datatypes.enums import QuantityUnits
from datatypes.epc.domain.epc import Epc


def portfolio_goal_values(enum_cls: Type[PortfolioGoal]) -> List[str]:
    return [e.value for e in enum_cls]


class Recommendation(Base):
    __tablename__ = "recommendation"

    id = Column(BigInteger, primary_key=True, autoincrement=True)
    property_id = Column(BigInteger, ForeignKey(PropertyModel.id), nullable=False)
    created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
    type = Column(String, nullable=False)
    measure_type = Column(String)
    description = Column(String, nullable=False)
    estimated_cost = Column(Float)
    default = Column(Boolean, nullable=False)
    starting_u_value = Column(Float)
    new_u_value = Column(Float)
    sap_points = Column(Float)
    heat_demand = Column(Float)
    kwh_savings = Column(Float)
    co2_equivalent_savings = Column(Float)
    energy_savings = Column(Float)
    energy_cost_savings = Column(Float)
    property_valuation_increase = Column(Float)
    rental_yield_increase = Column(Float)
    total_work_hours = Column(Float)
    labour_days = Column(Float)
    already_installed = Column(Boolean, nullable=False, default=False)


class RecommendationMaterials(Base):
    __tablename__ = "recommendation_materials"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)

    recommendation_id: Mapped[int] = mapped_column(
        BigInteger,
        ForeignKey("recommendation.id"),
        nullable=False,
    )

    material_id: Mapped[int] = mapped_column(
        BigInteger,
        ForeignKey(Material.id),
        nullable=False,
    )

    created_at: Mapped[datetime] = mapped_column(
        TIMESTAMP,
        nullable=False,
        server_default=func.now(),
    )

    depth: Mapped[float] = mapped_column(
        Float,
        nullable=False,
    )

    quantity: Mapped[float] = mapped_column(
        Float,
        nullable=False,
    )

    quantity_unit: Mapped[QuantityUnits] = mapped_column(
        Enum(QuantityUnits, values_callable=lambda x: [e.value for e in x]),
        nullable=False,
    )

    estimated_cost: Mapped[float] = mapped_column(
        Float,
        nullable=False,
    )


class PlanTypeEnum(enum.Enum):  # TODO: move this to domain?
    SOLAR_ECO4 = "solar_eco4"
    SOLAR_HHRSH_ECO4 = "solar_hhrsh_eco4"
    EMPTY_CAVITY_ECO = "empty_cavity_eco"
    PARTIAL_CAVITY_ECO = "partial_cavity_eco"
    EXTRACTION_ECO = "extraction_eco"


class PlanModel(Base):
    __tablename__ = "plan"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)

    name: Mapped[Optional[str]] = mapped_column(String, nullable=True, default="")

    portfolio_id: Mapped[int] = mapped_column(
        BigInteger, ForeignKey(Portfolio.id), nullable=False
    )

    property_id: Mapped[int] = mapped_column(
        BigInteger, ForeignKey(PropertyModel.id), nullable=False
    )

    scenario_id: Mapped[Optional[int]] = mapped_column(
        BigInteger, ForeignKey("scenario.id")
    )

    created_at: Mapped[datetime] = mapped_column(  # type: ignore
        TIMESTAMP, nullable=False, server_default=func.now()
    )

    is_default: Mapped[bool] = mapped_column(Boolean, nullable=False)

    valuation_increase_lower_bound: Mapped[Optional[float]] = mapped_column(Float)
    valuation_increase_upper_bound: Mapped[Optional[float]] = mapped_column(Float)
    valuation_increase_average: Mapped[Optional[float]] = mapped_column(Float)

    plan_type: Mapped[Optional[PlanTypeEnum]] = mapped_column(
        Enum(
            PlanTypeEnum,
            name="plan_type",
            values_callable=lambda e: [m.value for m in e],
            create_type=False,
        ),
        nullable=True,
    )

    post_sap_points: Mapped[Optional[float]] = mapped_column(Float)
    post_epc_rating: Mapped[Optional[Epc]] = mapped_column(Enum(Epc))
    post_co2_emissions: Mapped[Optional[float]] = mapped_column(Float)
    co2_savings: Mapped[Optional[float]] = mapped_column(Float)
    post_energy_bill: Mapped[Optional[float]] = mapped_column(Float)
    energy_bill_savings: Mapped[Optional[float]] = mapped_column(Float)
    post_energy_consumption: Mapped[Optional[float]] = mapped_column(Float)
    energy_consumption_savings: Mapped[Optional[float]] = mapped_column(Float)
    valuation_post_retrofit: Mapped[Optional[float]] = mapped_column(Float)
    valuation_increase: Mapped[Optional[float]] = mapped_column(Float)

    # Financial metrics, excluding funding
    cost_of_works: Mapped[Optional[float]] = mapped_column(Float)
    contingency_cost: Mapped[Optional[float]] = mapped_column(Float)


class PlanRecommendations(Base):
    __tablename__ = "plan_recommendations"

    id = Column(BigInteger, primary_key=True, autoincrement=True)
    plan_id = Column(BigInteger, ForeignKey("plan.id"), nullable=False)
    recommendation_id = Column(
        BigInteger, ForeignKey("recommendation.id"), nullable=False
    )


class ScenarioModel(Base):
    __tablename__ = "scenario"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String, nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        TIMESTAMP, nullable=False, server_default=func.now()
    )
    budget: Mapped[Optional[float]] = mapped_column(Float)
    portfolio_id: Mapped[int] = mapped_column(
        BigInteger, ForeignKey(Portfolio.id), nullable=False
    )
    housing_type: Mapped[str] = mapped_column(String, nullable=False)
    goal: Mapped[PortfolioGoal] = mapped_column(
        Enum(PortfolioGoal, values_callable=portfolio_goal_values, name="goal"),
        nullable=False,
    )
    goal_value: Mapped[str] = mapped_column(String, nullable=False)
    trigger_file_path: Mapped[str] = mapped_column(String, nullable=False)
    already_installed_file_path: Mapped[Optional[str]] = mapped_column(String)
    patches_file_path: Mapped[Optional[str]] = mapped_column(String)
    non_invasive_recommendations_file_path: Mapped[Optional[str]] = mapped_column(
        String
    )
    exclusions: Mapped[Optional[str]] = mapped_column(String)
    multi_plan: Mapped[bool] = mapped_column(Boolean, default=False)
    is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)

    # Add in the fields we need, which were previously sitting at the portfolio level
    cost: Mapped[Optional[float]] = mapped_column(Float)
    contingency: Mapped[Optional[float]] = mapped_column(Float)
    funding: Mapped[Optional[float]] = mapped_column(Float)
    total_work_hours: Mapped[Optional[float]] = mapped_column(Float)
    energy_savings: Mapped[Optional[float]] = mapped_column(Float)
    co2_equivalent_savings: Mapped[Optional[float]] = mapped_column(Float)
    energy_cost_savings: Mapped[Optional[float]] = mapped_column(Float)
    epc_breakdown_pre_retrofit: Mapped[Optional[str]] = mapped_column(String)
    epc_breakdown_post_retrofit: Mapped[Optional[str]] = mapped_column(String)
    number_of_properties: Mapped[Optional[int]] = mapped_column(BigInteger)
    n_units_to_retrofit: Mapped[Optional[int]] = mapped_column(BigInteger)
    co2_per_unit_pre_retrofit: Mapped[Optional[str]] = mapped_column(String)
    co2_per_unit_post_retrofit: Mapped[Optional[str]] = mapped_column(String)
    energy_bill_per_unit_pre_retrofit: Mapped[Optional[str]] = mapped_column(String)
    energy_bill_per_unit_post_retrofit: Mapped[Optional[str]] = mapped_column(String)
    energy_consumption_per_unit_pre_retrofit: Mapped[Optional[str]] = mapped_column(
        String
    )
    energy_consumption_per_unit_post_retrofit: Mapped[Optional[str]] = mapped_column(
        String
    )
    valuation_improvement_per_unit: Mapped[Optional[str]] = mapped_column(String)
    cost_per_unit: Mapped[Optional[str]] = mapped_column(String)
    cost_per_co2_saved: Mapped[Optional[str]] = mapped_column(String)
    cost_per_sap_point: Mapped[Optional[str]] = mapped_column(String)
    valuation_return_on_investment: Mapped[Optional[str]] = mapped_column(String)
    property_valuation_increase: Mapped[Optional[float]] = mapped_column(Float)
    labour_days: Mapped[Optional[float]] = mapped_column(Float)


class MeasureType(enum.Enum):
    air_source_heat_pump = "air_source_heat_pump"
    boiler_upgrade = "boiler_upgrade"
    high_heat_retention_storage_heaters = "high_heat_retention_storage_heaters"
    secondary_heating = "secondary_heating"

    roomstat_programmer_trvs = "roomstat_programmer_trvs"
    time_temperature_zone_control = "time_temperature_zone_control"
    cylinder_thermostat = "cylinder_thermostat"

    cavity_wall_insulation = "cavity_wall_insulation"
    extension_cavity_wall_insulation = "extension_cavity_wall_insulation"
    external_wall_insulation = "external_wall_insulation"
    internal_wall_insulation = "internal_wall_insulation"
    loft_insulation = "loft_insulation"
    flat_roof_insulation = "flat_roof_insulation"
    room_roof_insulation = "room_roof_insulation"
    solid_floor_insulation = "solid_floor_insulation"
    suspended_floor_insulation = "suspended_floor_insulation"

    double_glazing = "double_glazing"
    secondary_glazing = "secondary_glazing"
    draught_proofing = "draught_proofing"

    mechanical_ventilation = "mechanical_ventilation"
    low_energy_lighting = "low_energy_lighting"
    solar_pv = "solar_pv"
    hot_water_tank_insulation = "hot_water_tank_insulation"
    sealing_open_fireplace = "sealing_open_fireplace"


class InstalledMeasure(Base):
    __tablename__ = "installed_measure"

    id = Column(BigInteger, primary_key=True, autoincrement=True)
    uprn = Column(BigInteger, nullable=False)
    measure_type = Column(
        Enum(
            MeasureType,
            name="measure_type",
            values_callable=lambda e: [m.value for m in e],
            create_type=False,  # <-- critical
        ),
        nullable=False,
    )
    installed_at = Column(TIMESTAMP)
    sap_points = Column(Float)
    carbon_savings = Column(Float)
    kwh_savings = Column(Float)
    bill_savings = Column(Float)
    heat_demand_savings = Column(Float)
    source = Column(String)
    is_active = Column(Boolean, nullable=False, default=True)


def enum_values(e: Iterable[PlanTypeEnum]) -> list[str]:
    return [m.value for m in e]


class PlanPersistence(NamedTuple):
    plan: PlanModel
    scenario: ScenarioModel
