###
# This script contains methods for interacting with the property table in the database
###
import datetime
import pytz
from sqlalchemy.orm import Session
from backend.app.db.models.portfolio import (
    PropertyModel, PropertyCreationStatus, PortfolioStatus, PropertyTargetsModel, PropertyDetailsEpcModel,
    PropertyDetailsSpatial
)
from sqlalchemy.orm.exc import NoResultFound


def create_property(session: Session, portfolio_id: int, address: str, postcode: str, uprn: str) -> (int, bool):
    """
    This function will create a record for the property in the database if it does not exist.
    If it does exist, it will just update the updated_at field.
    :param session: The database session
    :param portfolio_id: The ID of the portfolio the property belongs to
    :param address: The address of the property
    :param postcode: The postcode of the property
    :return: The ID of the property and a boolean indicating whether it was created or not
    """

    try:
        # Attempt to fetch the existing property
        existing_property = session.query(PropertyModel).filter_by(
            uprn=uprn, portfolio_id=portfolio_id
        ).one()

        # Update the 'updated_at' field
        existing_property.updated_at = datetime.datetime.now(pytz.utc)

        # Merge the updated property back into the session
        session.merge(existing_property)
        session.flush()

        return existing_property.id, False

    except NoResultFound:
        # Property doesn't exist, create a new one
        new_property = PropertyModel(
            address=address,
            postcode=postcode,
            portfolio_id=portfolio_id,
            uprn=uprn,
            creation_status=PropertyCreationStatus.LOADING,
            status=PortfolioStatus.ASSESSMENT.value,
            has_pre_condition_report=False,
            has_recommendations=False
        )

        # Add the new property to the session
        session.add(new_property)

        session.flush()

        return new_property.id, True


def create_property_targets(
    session: Session, property_id: int, portfolio_id: int, epc_target=None, heat_demand_target=None
):
    """
    This function will create a record for the property targets in the database if it does not exist.
    :param session: The database session
    :param property_id: The ID of the property the targets belong to
    :param portfolio_id: The ID of the portfolio the property belongs to
    :param epc_target: Goal EPC value for the property
    :param heat_demand_target: Heat demand target for the property in kwh/m^2/year
    :return:
    """

    new_target = PropertyTargetsModel(
        property_id=property_id,
        portfolio_id=portfolio_id,
        epc=epc_target,
        heat_demand=heat_demand_target
    )
    session.add(new_target)
    session.flush()

    return True


def update_property_data(
    session: Session, property_id: int, portfolio_id: int, property_data: dict
):
    now = datetime.datetime.now(pytz.utc)

    try:
        # Attempt to fetch the existing property
        existing_property = session.query(PropertyModel).filter_by(
            id=property_id, portfolio_id=portfolio_id
        ).one()

        # Update the fields with the data in property_data
        for key, value in property_data.items():
            setattr(existing_property, key, value)

        existing_property.updated_at = now

        # Merge the updated property back into the session and flush
        session.merge(existing_property)
        session.flush()

    except NoResultFound:
        raise Exception(f"Property with property_id {property_id} and portfolio_id {portfolio_id} not found")

    return True


def create_property_details_epc(
    session: Session, property_details_epc: dict
):
    """
    This function will create or update a record for the property details EPC in the database.
    :param session: The database session
    :param property_details_epc: A dictionary containing details about the property EPC.
    :return: True if successful, False otherwise.
    """

    existing_record = session.query(PropertyDetailsEpcModel).filter_by(
        portfolio_id=property_details_epc["portfolio_id"],
        property_id=property_details_epc["property_id"]
    ).first()

    if existing_record:
        # If the record exists, update its fields
        for key, value in property_details_epc.items():
            setattr(existing_record, key, value)
    else:
        # If the record doesn't exist, create a new one
        new_property_details_epc = PropertyDetailsEpcModel(**property_details_epc)
        session.add(new_property_details_epc)

    session.flush()

    return True


def update_or_create_property_spatial_details(session: Session, uprn: int, property_details_spatial: dict):
    """
    Update an existing property details record or create a new one based on the UPRN.

    :param session: The SQLAlchemy session for database interaction.
    :param uprn: The unique property reference number (UPRN) of the property.
    :param property_details_spatial: A dictionary containing the spatial property details to store or update.
    :return: True if the operation is successful, otherwise raises an exception.
    """

    try:
        # Attempt to fetch the existing property details
        existing_property_details = session.query(PropertyDetailsSpatial).filter_by(
            uprn=uprn
        ).one()

        # Update the fields with the data in property_details
        for key, value in property_details_spatial.items():
            setattr(existing_property_details, key, value)

        # Merge the updated property details back into the session and flush
        session.merge(existing_property_details)
        session.flush()

    except NoResultFound:
        # Create a new record if not found
        new_property_details = PropertyDetailsSpatial(uprn=uprn, **property_details_spatial)
        session.add(new_property_details)
        session.flush()

    return True
