import enum
import pytz
import datetime
from sqlalchemy import (
    Column,
    Integer,
    BigInteger,
    Text,
    Boolean,
    Float,
    DateTime,
    Enum,
    ForeignKey,
    CheckConstraint,
)
from backend.app.db.base import Base
from backend.app.db.models.users import UserModel  # noqa
from backend.app.db.models.materials import MaterialType
from datatypes.epc.domain.epc import Epc


class PortfolioStatus(enum.Enum):
    SCOPING = "scoping"
    ASSESSMENT = "assessment"
    SURVEY = "survey"
    TENDERING = "tendering"
    PROJECT_UNDERWAY = "project underway"
    COMPLETION_ON_TRACK = "completion; status: on track"
    COMPLETION_DELAYED = "completion; status: delayed"
    COMPLETION_AT_RISK = "completion; status: at risk"
    COMPLETED = "completion; status: completed"
    NEEDS_REVIEW = "needs review"


class PortfolioGoal(enum.Enum):  # TODO: Move to domain?
    VALUATION_IMPROVEMENT = "Valuation Improvement"
    INCREASING_EPC = "Increasing EPC"
    REDUCING_CO2_EMISSIONS = "Reducing CO2 emissions"
    ENERGY_SAVINGS = "Energy Savings"
    NONE = "None"


class Portfolio(Base):
    __tablename__ = "portfolio"
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(Text, nullable=False)
    budget = Column(Float)
    status = Column(
        Enum(PortfolioStatus, values_callable=lambda x: [e.value for e in x]),
        nullable=False,
    )
    goal = Column(
        Enum(PortfolioGoal, values_callable=lambda x: [e.value for e in x]),
        nullable=False,
    )
    cost = Column(Float)
    number_of_properties = Column(Integer)
    co2_equivalent_savings = Column(
        Float
    )  # Unit is always tonnes so we don't need to store the unit
    energy_savings = Column(
        Float
    )  # Unit is always kWh so we don't need to store the unit
    energy_cost_savings = Column(
        Float
    )  # Unit is always £ so we don't need to store the unit for the moment
    property_valuation_increase = Column(
        Float
    )  # Unit is always £ so we don't need to store the unit for the moment
    rental_yield_increase = Column(
        Float
    )  # Unit is always £ so we don't need to store the unit for the moment
    total_work_hours = Column(Float)
    labour_days = Column(Float)
    created_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    updated_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    # Aggregations for summary
    epc_breakdown_pre_retrofit = Column(Text)
    epc_breakdown_post_retrofit = Column(Text)
    n_units_to_retrofit = Column(Integer)
    co2_per_unit_pre_retrofit = Column(Text)
    co2_per_unit_post_retrofit = Column(Text)
    energy_bill_per_unit_pre_retrofit = Column(Text)
    energy_bill_per_unit_post_retrofit = Column(Text)
    energy_consumption_per_unit_pre_retrofit = Column(Text)
    energy_consumption_per_unit_post_retrofit = Column(Text)
    valuation_improvement_per_unit = Column(Text)
    cost_per_unit = Column(Text)
    cost_per_co2_saved = Column(Text)
    cost_per_sap_point = Column(Text)
    valuation_return_on_investment = Column(Text)


class PropertyCreationStatus(enum.Enum):
    LOADING = "LOADING"
    READY = "READY"
    ERROR = "ERROR"


class PropertyModel(Base):
    __tablename__ = "property"
    id = Column(Integer, primary_key=True, autoincrement=True)
    portfolio_id = Column(Integer, ForeignKey("portfolio.id"), nullable=False)
    creation_status = Column(Enum(PropertyCreationStatus), nullable=False)
    uprn = Column(BigInteger)
    landlord_property_id = Column(Text)
    building_reference_number = Column(BigInteger)
    status = Column(
        Enum(PortfolioStatus, values_callable=lambda x: [e.value for e in x]),
        nullable=False,
    )
    address = Column(Text, nullable=False)
    postcode = Column(Text, nullable=False)
    has_pre_condition_report = Column(Boolean)
    has_recommendations = Column(Boolean)
    created_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    updated_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    property_type = Column(Text)
    built_form = Column(Text)
    local_authority = Column(Text)
    constituency = Column(Text)
    number_of_rooms = Column(Integer)
    year_built = Column(Text)
    tenure = Column(Text)
    current_epc_rating = Column(Enum(Epc))
    current_sap_points = Column(Float)
    current_valuation = Column(Float)
    # Following fields are for recording already installed adjustments to a property's SAP
    installed_measures_sap_point_adjustment = Column(Float)
    is_sap_points_adjusted_for_installed_measures = Column(Boolean, default=False)
    original_sap_points = Column(Float)

    # New for re-scoring - we will need to delete some of the redundant fields but there is a ticket for this
    lodged_sap_points = Column(Float)
    lodged_epc_rating = Column(Enum(Epc))


class FeatureRating(enum.Enum):
    VERY_GOOD = 5
    GOOD = 4
    AVERAGE = 3
    POOR = 2
    VERY_POOR = 1
    NA = None


rating_lookup = {
    "Very Good": FeatureRating.VERY_GOOD,
    "Good": FeatureRating.GOOD,
    "Average": FeatureRating.AVERAGE,
    "Poor": FeatureRating.POOR,
    "Very Poor": FeatureRating.VERY_POOR,
    "N/A": FeatureRating.NA,
}


def get_feature_rating_from_string(rating_str: str):
    return rating_lookup.get(rating_str, FeatureRating.NA)


class PropertyDetailsEpcModel(Base):
    __tablename__ = "property_details_epc"
    id = Column(Integer, primary_key=True, autoincrement=True)
    property_id = Column(Integer, ForeignKey("property.id"), nullable=False)
    portfolio_id = Column(Integer, ForeignKey("portfolio.id"), nullable=False)
    full_address = Column(Text)
    lodgement_date = Column(DateTime)
    is_expired = Column(Boolean)
    total_floor_area = Column(Float)
    walls = Column(Text)
    walls_rating = Column(
        Integer, CheckConstraint("walls_rating>=1 AND walls_rating<=5")
    )
    roof = Column(Text)
    roof_rating = Column(Integer, CheckConstraint("roof_rating>=1 AND roof_rating<=5"))
    floor = Column(Text)
    floor_rating = Column(
        Integer, CheckConstraint("floor_rating>=1 AND floor_rating<=5")
    )
    windows = Column(Text)
    windows_rating = Column(
        Integer, CheckConstraint("windows_rating>=1 AND windows_rating<=5")
    )
    heating = Column(Text)
    heating_rating = Column(
        Integer, CheckConstraint("heating_rating>=1 AND heating_rating<=5")
    )
    heating_controls = Column(Text)
    heating_controls_rating = Column(
        Integer,
        CheckConstraint("heating_controls_rating>=1 AND heating_controls_rating<=5"),
    )
    hot_water = Column(Text)
    hot_water_rating = Column(
        Integer, CheckConstraint("hot_water_rating>=1 AND hot_water_rating<=5")
    )
    lighting = Column(Text)
    lighting_rating = Column(
        Integer, CheckConstraint("lighting_rating>=1 AND lighting_rating<=5")
    )
    mainfuel = Column(Text)
    ventilation = Column(Text)
    solar_pv = Column(Text)
    solar_hot_water = Column(Text)
    wind_turbine = Column(Text)
    floor_height = Column(Float)
    number_heated_rooms = Column(Integer)
    heat_loss_corridor = Column(Boolean)
    unheated_corridor_length = Column(Float)
    number_of_open_fireplaces = Column(Integer)
    number_of_extensions = Column(Integer)
    number_of_storeys = Column(Integer)
    mains_gas = Column(Boolean)
    energy_tariff = Column(Text)
    primary_energy_consumption = Column(Float)
    co2_emissions = Column(Float)
    current_energy_demand = Column(Float)
    current_energy_demand_heating_hotwater = Column(Float)
    estimated = Column(Boolean, default=False)
    sap_05_overwritten = Column(Boolean, default=False)
    sap_05_score = Column(Integer)
    sap_05_epc_rating = Column(Enum(Epc))
    # Include estimates for energy bills, across the different types of energy
    heating_cost_current = Column(Float)
    hot_water_cost_current = Column(Float)
    lighting_cost_current = Column(Float)
    appliances_cost_current = Column(Float)
    gas_standing_charge = Column(Float)
    electricity_standing_charge = Column(Float)

    # Columns for re-baselining if we have an already installed measure
    original_co2_emissions = Column(Float)
    original_primary_energy_consumption = Column(Float)
    original_current_energy_demand = Column(Float)
    original_current_energy_demand_heating_hotwater = Column(Float)
    # Adjustments
    installed_measures_co2_adjustment = Column(Float)
    installed_measures_energy_demand_adjustment = Column(Float)
    installed_measures_total_energy_bill_adjustment = Column(Float)
    installed_measures_heat_demand_adjustment = Column(Float)
    is_epc_adjusted_for_installed_measures = Column(Boolean, default=False)

    # New columns - we'll need to delete some of the redundant fields, associated to "already installed" but
    # we have a ticket for this piece of work
    lodged_co2_emissions = Column(Float)
    lodged_heat_demand = Column(Float)
    has_been_remodelled = Column(Boolean, default=False)

    environment_impact_current = Column(Float)


class PropertyDetailsSpatial(Base):
    __tablename__ = "property_details_spatial"
    id = Column(Integer, primary_key=True, autoincrement=True)
    uprn = Column(Integer, nullable=False)
    x_coordinate = Column(Float)
    y_coordinate = Column(Float)
    latitude = Column(Float)
    longitude = Column(Float)
    conservation_status = Column(Boolean)
    is_listed_building = Column(Boolean)
    is_heritage_building = Column(Boolean)


class PropertyDetailsMeter(Base):
    __tablename__ = "property_details_meter"
    id = Column(Integer, primary_key=True, autoincrement=True)
    uprn = Column(Integer, nullable=False)
    energy_supplier = Column(Text)
    gas_supplier = Column(Text)
    meter_reading_total = Column(Float)
    meter_reading_electricity = Column(Float)
    meter_reading_gas = Column(Float)


class PropertyTargetsModel(Base):
    __tablename__ = "property_targets"
    id = Column(Integer, primary_key=True, autoincrement=True)
    property_id = Column(Integer, ForeignKey("property.id"), nullable=False)
    portfolio_id = Column(Integer, ForeignKey("portfolio.id"), nullable=False)
    created_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    epc = Column(Enum(Epc))
    heat_demand = Column(Text)


class PortfolioUsers(Base):
    __tablename__ = "portfolioUsers"
    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
    portfolioId = Column(Integer, ForeignKey("portfolio.id"), nullable=False)
    role = Column(Text, nullable=False)
    created_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    updated_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )


class PropertyInstalledMeasures(Base):
    """
    This model keeps a record of the installed measures for each property, at the UPRN level
    """

    __tablename__ = "property_installed_measures"
    id = Column(Integer, primary_key=True, autoincrement=True)
    uprn = Column(Integer, nullable=False)
    measure_type = Column(
        Enum(
            MaterialType,
            values_callable=lambda x: [e.value for e in x],
            create_constraint=False,
        ),
        nullable=False,
    )
    created_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
    installed_at = Column(
        DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
    )
