from collections.abc import Iterable
from typing import TypedDict

from folkugat_web.dal.sql import Connection, get_connection
from folkugat_web.model import search as search_model
from folkugat_web.model import temes as model
from folkugat_web.services import ngrams

PropertyRowTuple = tuple[int, int, str, str]


class PropertyRowDict(TypedDict):
    id: int | None
    tema_id: int
    field: str
    value: str


def property_to_row(property: model.Property) -> PropertyRowDict:
    return {
        "id": property.id,
        "tema_id": property.tema_id,
        "field": property.field.value,
        "value": property.value,
    }


def row_to_property(row: PropertyRowTuple) -> model.Property:
    return model.Property(
        id=row[0],
        tema_id=row[1],
        field=model.PropertyField(row[2]),
        value=row[3],
    )


class QueryData(TypedDict, total=False):
    id: int
    tema_id: int


def _filter_clause(
    property_id: int | None,
    tema_id: int | None,
) -> tuple[str, QueryData]:
    filter_clauses: list[str] = []
    filter_data: QueryData = {}

    if property_id is not None:
        filter_clauses.append("id = :id")
        filter_data["id"] = property_id

    if tema_id is not None:
        filter_clauses.append("tema_id = :tema_id")
        filter_data["tema_id"] = tema_id

    filter_clause = " AND ".join(filter_clauses)
    return filter_clause, filter_data


def get_properties(property_id: int | None = None, tema_id: int | None = None, con: Connection | None = None) -> Iterable[model.Property]:
    filter_clause, data = _filter_clause(property_id=property_id, tema_id=tema_id)
    if filter_clause:
        filter_clause = f"WHERE {filter_clause}"

    query = f"""
      SELECT
        id, tema_id, field, value
      FROM tema_properties
      {filter_clause}
    """
    with get_connection(con) as con:
        cur = con.cursor()
        _ = cur.execute(query, data)
        return map(row_to_property, cur.fetchall())


def insert_property(property: model.Property, con: Connection | None = None) -> model.Property:
    data = property_to_row(property)
    query = f"""
      INSERT INTO tema_properties
        (id, tema_id, field, value)
      VALUES
        (:id, :tema_id, :field, :value)
      RETURNING *
    """
    with get_connection(con) as con:
        cur = con.cursor()
        _ = cur.execute(query, data)
        row: PropertyRowTuple = cur.fetchone()
    evict_property_value_to_ngrams_cache()
    return row_to_property(row)


def create_property(tema_id: int, field: model.PropertyField | None = None, con: Connection | None = None) -> model.Property:
    new_property = model.Property(
        id=None,
        tema_id=tema_id,
        field=field or model.PropertyField.AUTOR,
        value="",
    )
    return insert_property(new_property, con=con)


def update_property(property: model.Property, con: Connection | None = None):
    data = property_to_row(property)
    query = """
      UPDATE tema_properties
      SET
        tema_id = :tema_id, field = :field, value = :value
      WHERE
        id = :id
    """
    with get_connection(con) as con:
        cur = con.cursor()
        _ = cur.execute(query, data)
    evict_property_value_to_ngrams_cache()


def delete_property(property_id: int, tema_id: int | None = None, con: Connection | None = None):
    filter_clause, data = _filter_clause(property_id=property_id, tema_id=tema_id)
    if filter_clause:
        filter_clause = f"WHERE {filter_clause}"

    query = f"""
      DELETE FROM tema_properties
      {filter_clause}
    """
    with get_connection(con) as con:
        cur = con.cursor()
        _ = cur.execute(query, data)
    evict_property_value_to_ngrams_cache()


_property_value_to_ngrams_cache: dict[str, search_model.NGrams] | None = None


def evict_property_value_to_ngrams_cache():
    global _property_value_to_ngrams_cache
    _property_value_to_ngrams_cache = None


def get_property_value_to_ngrams(con: Connection | None = None) -> dict[str, search_model.NGrams]:
    global _property_value_to_ngrams_cache
    if _property_value_to_ngrams_cache is None:
        _property_value_to_ngrams_cache = _get_property_value_to_ngrams(con)
    return _property_value_to_ngrams_cache


def _get_property_value_to_ngrams(con: Connection | None = None) -> dict[str, search_model.NGrams]:
    query = """
      SELECT value
      FROM tema_properties
    """
    with get_connection(con) as con:
        cur = con.cursor()
        _ = cur.execute(query)
        rows: list[tuple[str]] = cur.fetchall()
        props = {prop[0] for prop in rows}
        return {prop: ngrams.get_text_ngrams(prop.lower()) for prop in props}
