from __future__ import annotations

import json

import pytest

from app.core.exceptions import ConfigurationError
from app.schema.customization import apply_schema_customizations
from app.schema.loader import ColumnInfo, ForeignKeyInfo, TableInfo
from app.schema.relationship_mapper import build_relationship_graph
from app.schema.registry import _build_snapshot
from app.schema.vocabulary import build_vocabulary_index


def _snapshot():
    raw_tables = {
        "customers": TableInfo(
            name="customers",
            columns={
                "id": ColumnInfo("id", "INTEGER", False, primary_key=True),
                "display_name": ColumnInfo("display_name", "VARCHAR(255)", False),
                "payload": ColumnInfo("payload", "JSON", True),
            },
            primary_keys=["id"],
        ),
        "invoices": TableInfo(
            name="invoices",
            columns={
                "id": ColumnInfo("id", "INTEGER", False, primary_key=True),
                "customer_id": ColumnInfo("customer_id", "INTEGER", False),
            },
            primary_keys=["id"],
            foreign_keys=[ForeignKeyInfo(["customer_id"], "customers", ["id"])],
        ),
    }
    return _build_snapshot(raw_tables, build_relationship_graph(raw_tables))


def test_customizations_change_descriptions_and_add_safe_aliases(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text(json.dumps({"tables": {"customers": {
        "description": "Accounts receivable customers.",
        "aliases": ["client"],
        "columns": {"display_name": {"aliases": ["customer name"]}, "payload": {"enabled": False}},
    }}}), encoding="utf-8")

    snapshot = apply_schema_customizations(_snapshot(), str(path))
    vocab = build_vocabulary_index(snapshot)

    assert snapshot.tables["customers"].description == "Accounts receivable customers."
    assert "payload" not in snapshot.tables["customers"].columns
    assert snapshot.relationships  # hiding an unrelated column retains verified FKs
    assert vocab.resolve_table("client").schema_name == "customers"
    assert [r.schema_name for r in vocab.resolve_column("customer name", "customers")] == ["customers.display_name"]


def test_customizations_reject_identifiers_not_present_in_live_schema(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text('{"tables": {"invented_table": {}}}', encoding="utf-8")

    with pytest.raises(ConfigurationError, match="unknown tables"):
        apply_schema_customizations(_snapshot(), str(path))


def test_customizations_reject_unknown_column_in_strict_mode(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text('{"tables": {"invoices": {"columns": {"ghost_col": {}}}}}', encoding="utf-8")

    with pytest.raises(ConfigurationError, match="unknown columns"):
        apply_schema_customizations(_snapshot(), str(path))


# ---------------------------------------------------------------------------
# allow_stale=True (live schema-sync path): missing identifiers are skipped
# with a log, never invented, and valid entries are still applied.
# ---------------------------------------------------------------------------


def test_allow_stale_skips_unknown_table_but_applies_valid_entries(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text(json.dumps({"tables": {
        "invented_table": {"description": "Ghost table"},
        "customers": {"description": "Accounts receivable customers."},
    }}), encoding="utf-8")

    snapshot = apply_schema_customizations(_snapshot(), str(path), allow_stale=True)

    assert "invented_table" not in snapshot.tables
    assert snapshot.tables["customers"].description == "Accounts receivable customers."


def test_allow_stale_skips_unknown_column_but_applies_known_ones(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text(json.dumps({"tables": {"invoices": {"columns": {
        "ghost_col": {"description": "Ghost column"},
        "customer_id": {"aliases": ["invoice customer ref"]},
    }}}}), encoding="utf-8")

    snapshot = apply_schema_customizations(_snapshot(), str(path), allow_stale=True)

    assert "ghost_col" not in snapshot.tables["invoices"].columns
    assert snapshot.tables["invoices"].columns["customer_id"].aliases == ["invoice customer ref"]


def test_allow_stale_skips_stale_platform_column(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text('{"tables": {"invoices": {"platform_column": "ghost_col"}}}', encoding="utf-8")

    snapshot = apply_schema_customizations(_snapshot(), str(path), allow_stale=True)

    assert snapshot.tables["invoices"].platform_column is None


def test_allow_stale_skips_computed_metric_anchored_to_unknown_table(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text(json.dumps({"computed_fields": {"profit": {
        "table": "invented_table",
        "description": "Profit",
        "aliases": ["profit"],
        "builder": "net_sum",
        "components": [{"table": "invoices", "value_column": "customer_id", "sign": "add"}],
    }}}), encoding="utf-8")

    snapshot = apply_schema_customizations(_snapshot(), str(path), allow_stale=True)

    # The metric must simply not be offered anywhere - never invented on a
    # different table.
    assert all("profit" not in table.columns for table in snapshot.tables.values())


def test_strict_mode_rejects_stale_computed_metric_anchor(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text(json.dumps({"computed_fields": {"profit": {
        "table": "invented_table",
        "description": "Profit",
        "aliases": ["profit"],
        "builder": "net_sum",
        "components": [{"table": "invoices", "value_column": "customer_id", "sign": "add"}],
    }}}), encoding="utf-8")

    with pytest.raises(ConfigurationError, match="unknown table"):
        apply_schema_customizations(_snapshot(), str(path))


def test_hiding_a_relationship_column_removes_that_relationship(tmp_path):
    path = tmp_path / "schema.json"
    path.write_text('{"tables": {"invoices": {"columns": {"customer_id": {"enabled": false}}}}}', encoding="utf-8")

    snapshot = apply_schema_customizations(_snapshot(), str(path))

    assert snapshot.relationships == []
