from typing import Optional
from sqlalchemy.orm import Session
from sqlalchemy import func
from backend.app.db.models.addresses import PostcodeSearch
from utils.logger import setup_logger

logger = setup_logger()


def _get_associated_records(results, uprn, uprn_key="UPRN"):
    matched_record = []
    for x in results:
        if "DPA" in x:
            if x["DPA"].get(uprn_key) == str(uprn):
                matched_record.append(x["DPA"])
        else:
            if x["LPI"].get(uprn_key) == str(uprn):
                matched_record.append(x["LPI"])

    return matched_record


def get_associated_uprns(postcode_search: Optional[PostcodeSearch], uprn: str | int):
    """
    Given a postcode and UPRN, for a remote assessment, fetch all associated UPRNs, based
    on parent UPRN. This will be properties in the same building

    Parent UPRN is referenced in the following docs:
    https://static.geoplace.co.uk/downloads/GeoPlace-Data-Entry-Conventions-Best-Practice-for-Addresses.pdf

    :param PostcodeSearch postcode_search: The postcode search record
    :param uprn: The UPRN string to match
    :return: The matching PostcodeSearch record, or None if not found
    """

    if not postcode_search:
        return []

    if isinstance(uprn, int):
        # For this, coerce to string
        uprn = str(uprn)

    matched_record = _get_associated_records(results=postcode_search.result_data["results"], uprn=uprn)

    if len(matched_record) != 1:
        return []

    if not matched_record[0].get("PARENT_UPRN"):
        logger.info("No parent UPRN found, cannot get associated records")
        return []

    associated_records = _get_associated_records(
        results=postcode_search.result_data["results"], uprn=matched_record[0]["PARENT_UPRN"], uprn_key="PARENT_UPRN"
    )
    # We now fetch all UPRNS with the same parent UPRN
    associated_uprns = [int(x["UPRN"]) for x in associated_records if x["UPRN"] != str(uprn)]

    return associated_uprns


def get_by_postcodes(session: Session, postcodes: list[str]) -> dict[str, PostcodeSearch]:
    """
    Given a list of postcodes, retrieves postcode data from the database form the PostcodeSearch table
    :param session:
    :param postcodes:
    :return:
    """
    if not postcodes:
        return {}

    normalised = {p.upper() for p in postcodes if p}

    records = (
        session.query(PostcodeSearch)
        .filter(func.upper(PostcodeSearch.postcode).in_(normalised))
        .all()
    )

    return {r.postcode.upper(): r for r in records}


def get_associated_uprns_from_record(record: PostcodeSearch, uprn: str) -> list[int]:
    """
    Given the postcode sra
    :param record:
    :param uprn:
    :return:
    """
    if not record:
        return []

    matched_record = _get_associated_records(
        results=record.result_data["results"],
        uprn=uprn
    )

    if len(matched_record) != 1:
        return []

    parent_uprn = matched_record[0].get("PARENT_UPRN")
    if not parent_uprn:
        return []

    associated_records = _get_associated_records(
        results=record.result_data["results"],
        uprn=parent_uprn,
        uprn_key="PARENT_UPRN"
    )

    return [
        int(x["UPRN"])
        for x in associated_records
        if x["UPRN"] != str(uprn)
    ]
