"""Tests para detect_declared_keys_duckdb."""

import duckdb
import pytest

from .detect_declared_keys_duckdb import detect_declared_keys_duckdb


@pytest.fixture
def db(tmp_path):
    """DuckDB temporal con claves declaradas.

    - customers(id PRIMARY KEY, name)
    - orders(id PRIMARY KEY, customer_id REFERENCES customers(id), amt)

    Esto declara dos PRIMARY KEY (customers.id, orders.id) y una FOREIGN KEY
    (orders.customer_id -> customers.id). DuckDB ademas genera constraints
    NOT NULL para las columnas PK, que la funcion debe ignorar.
    """
    path = str(tmp_path / "keys_test.duckdb")
    con = duckdb.connect(path)
    con.execute("CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT)")
    con.execute(
        "CREATE TABLE orders("
        "  id INTEGER PRIMARY KEY,"
        "  customer_id INTEGER REFERENCES customers(id),"
        "  amt DOUBLE"
        ")"
    )
    con.close()
    return path


def _pk_for(res, table):
    """Devuelve la entrada primary_keys cuya tabla es `table`, o None."""
    for pk in res["primary_keys"]:
        if pk["table"] == table:
            return pk
    return None


def test_golden_detecta_pks_y_fk(db):
    """Golden: detecta las dos PK y la FK declaradas, con valores concretos."""
    res = detect_declared_keys_duckdb(db)
    assert res["status"] == "ok"

    # PRIMARY KEY de customers y de orders.
    pk_customers = _pk_for(res, "customers")
    pk_orders = _pk_for(res, "orders")
    assert pk_customers is not None
    assert pk_customers["columns"] == ["id"]
    assert pk_orders is not None
    assert pk_orders["columns"] == ["id"]

    # FOREIGN KEY orders.customer_id -> customers.id.
    assert len(res["foreign_keys"]) == 1
    fk = res["foreign_keys"][0]
    assert fk["table"] == "orders"
    assert fk["columns"] == ["customer_id"]
    assert fk["referenced_table"] == "customers"
    assert fk["referenced_columns"] == ["id"]

    # tables incluye ambas (origen de algun constraint).
    assert res["tables"] == ["customers", "orders"]


def test_golden_ignora_not_null_y_check(db):
    """NOT NULL (auto-generado por las PK) no aparece como clave."""
    res = detect_declared_keys_duckdb(db)
    assert res["status"] == "ok"
    # Solo 2 PK reales (no las NOT NULL que DuckDB genera por cada columna PK).
    assert len(res["primary_keys"]) == 2
    # No hay UNIQUE declarado en este schema.
    assert res["unique"] == []


def test_edge_filtra_por_tabla_orders(db):
    """Edge table='orders': PK de orders + su FK; NO la PK de customers."""
    res = detect_declared_keys_duckdb(db, table="orders")
    assert res["status"] == "ok"

    # Solo la PK de orders.
    assert len(res["primary_keys"]) == 1
    assert res["primary_keys"][0]["table"] == "orders"
    assert res["primary_keys"][0]["columns"] == ["id"]
    # La PK de customers NO esta.
    assert _pk_for(res, "customers") is None

    # La FK de orders si esta (origen = orders).
    assert len(res["foreign_keys"]) == 1
    assert res["foreign_keys"][0]["table"] == "orders"
    assert res["foreign_keys"][0]["referenced_table"] == "customers"

    # tables solo contiene orders (la dueña de los constraints emitidos).
    assert res["tables"] == ["orders"]


def test_edge_filtra_por_tabla_customers(db):
    """Edge table='customers': solo su PK; ninguna FK (orders queda fuera)."""
    res = detect_declared_keys_duckdb(db, table="customers")
    assert res["status"] == "ok"
    assert len(res["primary_keys"]) == 1
    assert res["primary_keys"][0]["table"] == "customers"
    assert res["foreign_keys"] == []
    assert res["tables"] == ["customers"]


def test_edge_unique_declarado(tmp_path):
    """Edge: una constraint UNIQUE declarada aparece en `unique`."""
    path = str(tmp_path / "unique_test.duckdb")
    con = duckdb.connect(path)
    con.execute("CREATE TABLE products(sku INTEGER UNIQUE, name TEXT)")
    con.close()

    res = detect_declared_keys_duckdb(path)
    assert res["status"] == "ok"
    assert len(res["unique"]) == 1
    assert res["unique"][0]["table"] == "products"
    assert res["unique"][0]["columns"] == ["sku"]
    assert res["primary_keys"] == []
    assert res["foreign_keys"] == []
    assert res["tables"] == ["products"]


def test_edge_sin_constraints_listas_vacias(tmp_path):
    """Edge: tabla sin PK/FK/UNIQUE -> todas las listas vacias, status ok."""
    path = str(tmp_path / "no_keys.duckdb")
    con = duckdb.connect(path)
    con.execute("CREATE TABLE log(a INTEGER, b INTEGER)")
    con.close()

    res = detect_declared_keys_duckdb(path)
    assert res["status"] == "ok"
    assert res["primary_keys"] == []
    assert res["foreign_keys"] == []
    assert res["unique"] == []
    assert res["tables"] == []


def test_error_db_inexistente_no_lanza(tmp_path):
    """Error: db_path inexistente -> status error, sin lanzar excepcion."""
    path = str(tmp_path / "does_not_exist.duckdb")
    res = detect_declared_keys_duckdb(path)
    assert res["status"] == "error"
    assert isinstance(res["error"], str)
    assert res["error"] != ""


def test_shape_resultado(db):
    """El retorno tiene exactamente las claves esperadas."""
    res = detect_declared_keys_duckdb(db)
    assert set(res.keys()) == {
        "status",
        "primary_keys",
        "foreign_keys",
        "unique",
        "tables",
    }
    for pk in res["primary_keys"]:
        assert set(pk.keys()) == {"table", "columns"}
    for fk in res["foreign_keys"]:
        assert set(fk.keys()) == {
            "table",
            "columns",
            "referenced_table",
            "referenced_columns",
        }
