from typing import Optional

from core.database.external_data_temp_table_builder import ExternalDataTempTableBuilder


class SQLSelectBuilder:

    def __init__(self, table_name: str, primary_key: str):
        self._table_name = table_name
        self._primary_key = primary_key

        self._temp_tables: dict[str, ExternalDataTempTableBuilder] = {}
        self._to_use_temp_tables: list[str] = []
        self._attributes: list[str] = []
        self._tables: list[str] = [table_name]
        self._joins: dict[str, (str, str)] = {}
        self._conditions: list[str] = []
        self._order_by: str = ""
        self._limit: Optional[int] = None
        self._offset: Optional[int] = None

    def with_temp_table(
        self, temp_table: ExternalDataTempTableBuilder
    ) -> "SQLSelectBuilder":
        self._temp_tables[temp_table.table_name] = temp_table
        return self

    def use_temp_table(self, temp_table_name: str):
        if temp_table_name not in self._temp_tables:
            raise ValueError(f"Temp table {temp_table_name} not found.")

        self._to_use_temp_tables.append(temp_table_name)

    def with_attribute(self, attr: str, ignore_table_name=False) -> "SQLSelectBuilder":
        if not ignore_table_name and not attr.startswith(self._table_name):
            attr = f"{self._table_name}.{attr}"

        self._attributes.append(attr)
        return self

    def with_foreign_attribute(self, attr: str) -> "SQLSelectBuilder":
        self._attributes.append(attr)
        return self

    def with_table(self, table_name: str) -> "SQLSelectBuilder":
        self._tables.append(table_name)
        return self

    def _check_prefix(self, attr: str) -> str:
        assert attr is not None

        valid_prefixes = [
            "levenshtein",
            self._table_name,
            *self._joins.keys(),
            *self._temp_tables.keys(),
        ]
        if not any(attr.startswith(f"{prefix}.") for prefix in valid_prefixes):
            attr = f"{self._table_name}.{attr}"

        return attr

    def with_value_condition(
        self, attr: str, operator: str, value: str
    ) -> "SQLSelectBuilder":
        attr = self._check_prefix(attr)
        self._conditions.append(f"{attr} {operator} {value}")
        return self

    def with_levenshtein_condition(self, condition: str) -> "SQLSelectBuilder":
        self._conditions.append(condition)
        return self

    def with_condition(self, attr: str, operator: str) -> "SQLSelectBuilder":
        attr = self._check_prefix(attr)
        self._conditions.append(f"{attr} {operator}")
        return self

    def with_grouped_conditions(self, conditions: list[str]) -> "SQLSelectBuilder":
        self._conditions.append(f"({' AND '.join(conditions)})")
        return self

    def with_left_join(self, table: str, on: str) -> "SQLSelectBuilder":
        if table in self._joins:
            self._joins[table] = (f"{self._joins[table][0]} AND {on}", "LEFT")

        self._joins[table] = (on, "LEFT")
        return self

    def with_inner_join(self, table: str, on: str) -> "SQLSelectBuilder":
        if table in self._joins:
            self._joins[table] = (f"{self._joins[table][0]} AND {on}", "INNER")

        self._joins[table] = (on, "INNER")
        return self

    def with_right_join(self, table: str, on: str) -> "SQLSelectBuilder":
        if table in self._joins:
            self._joins[table] = (f"{self._joins[table][0]} AND {on}", "RIGHT")

        self._joins[table] = (on, "RIGHT")
        return self

    def with_limit(self, limit: int) -> "SQLSelectBuilder":
        self._limit = limit
        return self

    def with_offset(self, offset: int) -> "SQLSelectBuilder":
        self._offset = offset
        return self

    def with_order_by(self, column: str, direction: str = "ASC") -> "SQLSelectBuilder":
        self._order_by = f"{column} {direction}"
        return self

    async def _handle_temp_table_use(self, query) -> str:
        new_query = ""

        for temp_table_name in self._to_use_temp_tables:
            temp_table = self._temp_tables[temp_table_name]
            new_query += await self._temp_tables[temp_table_name].build()
            self.with_left_join(
                temp_table.table_name,
                f"{temp_table.join_ref_table}.{self._primary_key} = {temp_table.table_name}.{temp_table.primary_key}",
            )

        return f"{new_query} {query}" if new_query != "" else query

    async def build(self) -> str:
        query = await self._handle_temp_table_use("")

        attributes = ", ".join(self._attributes) if self._attributes else "*"
        query += f"SELECT {attributes} FROM {", ".join(self._tables)}"

        for join in self._joins:
            query += f" {self._joins[join][1]} JOIN {join} ON {self._joins[join][0]}"

        if self._conditions:
            query += " WHERE " + " AND ".join(self._conditions)

        if self._order_by:
            query += f" ORDER BY {self._order_by}"

        if self._limit is not None:
            query += f" LIMIT {self._limit}"

        if self._offset is not None:
            query += f" OFFSET {self._offset}"

        return query
