from __future__ import annotations

from collections import defaultdict
from dataclasses import dataclass, field

from ...metamodel.builtins import is_entity_type
from ...metamodel.metamodel import Field, Relation, ScalarType
from .model_analysis import ModelAnalysis, TableInfo, TableKind


@dataclass(frozen=True)
class SQL2ColumnShape:
    column_name: str
    source_relation_id: int
    source_field_id: int | None
    source_field_index: int | None
    is_key: bool
    is_id: bool


@dataclass(frozen=True)
class SQL2TableShape:
    owner_relation_id: int
    table_name: str
    table_kind: TableKind
    key_column_names: tuple[str, ...]
    columns: tuple[SQL2ColumnShape, ...]
    relation_ids: tuple[int, ...]


@dataclass
class SQL2TableShapeSidecar:
    tables: tuple[SQL2TableShape, ...]
    table_by_owner_relation_id: dict[int, SQL2TableShape] = field(default_factory=dict)
    table_owner_by_relation_id: dict[int, int] = field(default_factory=dict)
    table_name_by_relation_id: dict[int, str] = field(default_factory=dict)
    property_column_by_relation_id: dict[int, str] = field(default_factory=dict)
    link_table_by_relation_id: dict[int, str] = field(default_factory=dict)

    def table_for_relation_id(self, relation_id: int) -> SQL2TableShape | None:
        owner_id = self.table_owner_by_relation_id.get(relation_id)
        if owner_id is None:
            return None
        return self.table_by_owner_relation_id.get(owner_id)


def _is_supported_relation(relation: Relation) -> bool:
    if isinstance(relation, ScalarType):
        return is_entity_type(relation)
    return bool(relation.fields)


def _field_index(field: Field) -> int | None:
    for ix, rel_field in enumerate(field._relation.fields):
        if rel_field.id == field.id:
            return ix
    return None


def _is_key_column(table: TableInfo, source: Relation | Field, *, is_id: bool) -> bool:
    if is_id:
        return True
    if table.kind != TableKind.RELATIONSHIP:
        return False
    if isinstance(source, Field):
        relation = source._relation
        has_marked_keys = any(field.input for field in relation.fields)
        if has_marked_keys:
            return bool(source.input)
        return True
    return False


def _build_table_shape(
    table: TableInfo,
    *,
    table_name: str,
    relation_ids: set[int],
) -> SQL2TableShape:
    columns: list[SQL2ColumnShape] = []
    key_column_names: list[str] = []
    for column in table.columns:
        source = column.column
        if isinstance(source, Field):
            source_relation_id = source._relation.id
            source_field_id = source.id
            source_field_index = _field_index(source)
        else:
            source_relation_id = source.id
            source_field_id = None
            source_field_index = None

        is_key = _is_key_column(table, source, is_id=column.is_id)
        if is_key:
            key_column_names.append(column.name)

        columns.append(
            SQL2ColumnShape(
                column_name=column.name,
                source_relation_id=source_relation_id,
                source_field_id=source_field_id,
                source_field_index=source_field_index,
                is_key=is_key,
                is_id=column.is_id,
            )
        )

    # Keep relationship-key signatures non-empty by default for link tables.
    if table.kind == TableKind.RELATIONSHIP and not key_column_names and columns:
        key_column_names = [column.column_name for column in columns]

    return SQL2TableShape(
        owner_relation_id=table.relation.id,
        table_name=table_name,
        table_kind=table.kind,
        key_column_names=tuple(key_column_names),
        columns=tuple(columns),
        relation_ids=tuple(sorted(relation_ids)),
    )


def _canonical_table_names_by_owner(analysis: ModelAnalysis) -> dict[int, str]:
    ordered_tables = sorted(
        analysis.table_info.values(),
        key=lambda table: (table.name.casefold(), table.name, table.relation.id),
    )
    canonical: dict[int, str] = {}
    used_casefold: set[str] = set()
    for table in ordered_tables:
        owner_id = table.relation.id
        if owner_id in canonical:
            continue
        candidate = table.name
        if candidate.casefold() in used_casefold:
            base = f"{table.name}__r{owner_id}"
            candidate = base
            suffix = 2
            while candidate.casefold() in used_casefold:
                candidate = f"{base}_{suffix}"
                suffix += 1
        canonical[owner_id] = candidate
        used_casefold.add(candidate.casefold())
    return canonical


def build_sql2_table_shape_sidecar(analysis: ModelAnalysis) -> SQL2TableShapeSidecar:
    canonical_name_by_owner = _canonical_table_names_by_owner(analysis)
    relation_ids_by_owner: dict[int, set[int]] = defaultdict(set)
    table_owner_by_relation_id: dict[int, int] = {}
    table_name_by_relation_id: dict[int, str] = {}
    property_column_by_relation_id: dict[int, str] = {}
    link_table_by_relation_id: dict[int, str] = {}

    relations = [*analysis.model.types, *analysis.model.relations]
    ordered_relations = sorted(relations, key=lambda relation: (relation.id, relation.name))
    for relation in ordered_relations:
        if not _is_supported_relation(relation):
            continue
        table = analysis.get_table_info(relation)
        if table is None:
            column = analysis.get_column_info(relation)
            table = column.table if column is not None else None
        if table is None:
            continue

        owner_id = table.relation.id
        relation_ids_by_owner[owner_id].add(relation.id)
        table_owner_by_relation_id[relation.id] = owner_id
        table_name_by_relation_id[relation.id] = canonical_name_by_owner.get(owner_id, table.name)

        if relation is not table.relation:
            column = analysis.get_column_info(relation)
            if column is not None and not column.is_id:
                property_column_by_relation_id[relation.id] = column.name

        if relation is table.relation and table.kind == TableKind.RELATIONSHIP:
            link_table_by_relation_id[relation.id] = table_name_by_relation_id[relation.id]

    table_by_owner_relation_id: dict[int, SQL2TableShape] = {}
    for owner_id in sorted(relation_ids_by_owner):
        owner_table = next(
            (table for table in analysis.table_info.values() if table.relation.id == owner_id),
            None,
        )
        if owner_table is None:
            continue
        table_shape = _build_table_shape(
            owner_table,
            table_name=canonical_name_by_owner.get(owner_id, owner_table.name),
            relation_ids=relation_ids_by_owner[owner_id],
        )
        table_by_owner_relation_id[owner_id] = table_shape

    tables = tuple(
        table_by_owner_relation_id[owner_id]
        for owner_id in sorted(
            table_by_owner_relation_id,
            key=lambda rid: (
                table_by_owner_relation_id[rid].table_name,
                rid,
            ),
        )
    )
    return SQL2TableShapeSidecar(
        tables=tables,
        table_by_owner_relation_id=table_by_owner_relation_id,
        table_owner_by_relation_id=table_owner_by_relation_id,
        table_name_by_relation_id=table_name_by_relation_id,
        property_column_by_relation_id=property_column_by_relation_id,
        link_table_by_relation_id=link_table_by_relation_id,
    )
