"""TDD L14-S6 — Integrare Layer 2/3 in editor (suggestion-only, DUPA 5.15).

Scenarii acoperite:
- F1-regression CRITIC: SILVER/shared GOLD NU auto-trimit (resolve_prestatii neschimbat)
- pending_unmapped include sugestie GOLD partajat > SILVER > embeddings (precedenta Eng-F2)
- record_human_validation apelat la confirmare umana (POST /mapari -> shared_mappings)
- Degradare gratioasa cand embeddings indisponibil (mock is_available=False)
- Separare structurala #13: resolve_prestatii/load_mapping NU citesc tabelele de sugestii
"""

from __future__ import annotations

import os
import tempfile

import pytest


# --------------------------------------------------------------------------- #
# Fixtures                                                                    #
# --------------------------------------------------------------------------- #

@pytest.fixture()
def env(monkeypatch):
    """DB temporara cu schema initiata, auth dezactivata (mod dev)."""
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "l14_s6_test.db"))
    monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
    from app.config import get_settings
    get_settings.cache_clear()
    from app.db import init_db
    init_db()
    yield monkeypatch
    get_settings.cache_clear()


@pytest.fixture()
def conn(env):
    from app.db import get_connection
    c = get_connection()
    # Seed nomenclator (OE-1, OE-2, OE-3, OE-4 suficient pentru teste)
    c.executemany(
        "INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
        [
            ("OE-1", "REPARATIE MOTOR"),
            ("OE-2", "INTRETINERE"),
            ("OE-3", "REVIZIE PERIODICA"),
            ("OE-4", "REGLARE"),
        ],
    )
    c.commit()
    yield c
    c.close()


@pytest.fixture()
def client(env):
    from app.main import app
    from fastapi.testclient import TestClient
    with TestClient(app) as c:
        yield c


# --------------------------------------------------------------------------- #
# F1-regression CRITIC: SILVER/shared GOLD NU auto-trimit                     #
# --------------------------------------------------------------------------- #

def test_f1_silver_nu_auto_trimite(conn):
    """CRITICAL F1: un cod in SILVER (mapping_suggestions) NU produce auto-trimitere.

    resolve_prestatii cu mapping gol + SILVER existent -> operatie ramane nemapata.
    Submissionul ar ramane needs_mapping, NU queued.
    """
    from app.shared_store import seed_suggestions
    from app.mapping import resolve_prestatii

    seed_suggestions(conn, [
        {"denumire": "Revizie periodica", "cod_prestatie": "OE-3", "source": "llm", "confidence": 0.95},
    ])
    conn.commit()

    # resolve_prestatii cu mapping gol -> SILVER nu se vede
    resolved, unmapped = resolve_prestatii(
        [{"cod_op_service": "OP-REV", "denumire": "Revizie periodica"}],
        {},  # operations_mapping gol
    )
    # Operatia ramane nemapata (SILVER nu e in resolve, #13)
    assert resolved[0]["cod_prestatie"] is None
    assert len(unmapped) == 1


def test_f1_shared_gold_nu_auto_trimite(conn):
    """CRITICAL F1: un cod in shared_mappings (GOLD partajat) NU produce auto-trimitere.

    resolve_prestatii cu mapping gol + shared GOLD existent -> operatie ramane nemapata.
    """
    from app.shared_store import record_human_validation
    from app.mapping import resolve_prestatii

    record_human_validation(conn, "Schimb ulei motor", "OE-3")
    conn.commit()

    # resolve_prestatii cu mapping gol -> GOLD partajat nu se vede
    resolved, unmapped = resolve_prestatii(
        [{"cod_op_service": "OP-ULEI", "denumire": "Schimb ulei motor"}],
        {},  # operations_mapping gol
    )
    # Operatia ramane nemapata (GOLD partajat nu e in resolve, #13)
    assert resolved[0]["cod_prestatie"] is None
    assert len(unmapped) == 1


def test_f1_load_mapping_nu_citeste_shared_gold(conn):
    """Separare #13: load_mapping NU returneaza coduri din shared_mappings."""
    from app.shared_store import record_human_validation
    from app.mapping import load_mapping

    record_human_validation(conn, "Revizie anuala", "OE-3")
    conn.commit()

    mapping = load_mapping(conn, account_id=1)
    # GOLD partajat nu trebuie sa apara in load_mapping (citit de resolve_prestatii)
    assert "Revizie anuala" not in mapping
    # Maparea propriu-zisa (operations_mapping) ramane goala
    assert len(mapping) == 0


# --------------------------------------------------------------------------- #
# enrich_suggestions: GOLD partajat > SILVER > embeddings                     #
# --------------------------------------------------------------------------- #

def test_enrich_fara_surse_returneaza_none(conn):
    """Fara GOLD/SILVER/embedding -> sugestie_principala = None."""
    from app.mapping import enrich_suggestions

    result = enrich_suggestions(conn, "Operatie inexistenta")
    assert result["sugestie_principala"] is None
    assert result["surse"]["gold_partajat"] is None
    assert result["surse"]["silver"] is None
    assert result["surse"]["embedding"] is None


def test_enrich_include_gold_partajat(conn):
    """enrich_suggestions returneaza sugestie GOLD partajat cand shared_mappings are match."""
    from app.shared_store import record_human_validation
    from app.mapping import enrich_suggestions

    record_human_validation(conn, "Schimb ulei", "OE-3")
    conn.commit()

    result = enrich_suggestions(conn, "Schimb ulei")

    assert result["sugestie_principala"] is not None
    assert result["sugestie_principala"]["cod_prestatie"] == "OE-3"
    assert result["sugestie_principala"]["sursa"] == "gold_partajat"
    assert result["surse"]["gold_partajat"] == "OE-3"


def test_enrich_include_silver(conn):
    """enrich_suggestions returneaza sugestie SILVER cand mapping_suggestions are match."""
    from app.shared_store import seed_suggestions
    from app.mapping import enrich_suggestions

    seed_suggestions(conn, [
        {"denumire": "Reparatie motor", "cod_prestatie": "OE-1", "source": "llm", "confidence": 0.9},
    ])
    conn.commit()

    result = enrich_suggestions(conn, "Reparatie motor")

    assert result["sugestie_principala"] is not None
    assert result["sugestie_principala"]["cod_prestatie"] == "OE-1"
    assert result["sugestie_principala"]["sursa"] == "silver"
    assert result["surse"]["silver"] == "OE-1"


def test_enrich_precedenta_gold_peste_silver(conn):
    """Precedenta Eng-F2: GOLD partajat castiga fata de SILVER cand ambele exista."""
    from app.shared_store import seed_suggestions, record_human_validation
    from app.mapping import enrich_suggestions

    # SILVER spune OE-1, GOLD spune OE-3
    seed_suggestions(conn, [
        {"denumire": "Verificare tehnica", "cod_prestatie": "OE-1", "source": "llm", "confidence": 0.8},
    ])
    record_human_validation(conn, "Verificare tehnica", "OE-3")
    conn.commit()

    result = enrich_suggestions(conn, "Verificare tehnica")

    assert result["sugestie_principala"] is not None
    assert result["sugestie_principala"]["cod_prestatie"] == "OE-3"
    assert result["sugestie_principala"]["sursa"] == "gold_partajat"
    # SILVER prezent dar nu castiga
    assert result["surse"]["silver"] == "OE-1"
    assert result["surse"]["gold_partajat"] == "OE-3"


def test_enrich_degradare_embeddings_indisponibil(conn, monkeypatch):
    """Degradare gratioasa (#16b): cand embeddings nu e disponibil, nu eroare."""
    import app.embeddings as emb_mod
    monkeypatch.setattr(emb_mod, "is_available", lambda: False)

    from app.mapping import enrich_suggestions

    # Fara surse -> sugestie_principala = None, fara exceptie
    result = enrich_suggestions(conn, "Operatie demo", include_embeddings=True)
    assert result["sugestie_principala"] is None
    assert result["surse"]["embedding"] is None


def test_enrich_corpus_gol_nu_incarca_modelul(conn, monkeypatch):
    """Bug fix (code-review): enrich_suggestions NU lazy-load-eaza modelul de 220MB
    cand corpus-ul embeddings e gol.

    Implementarea veche apela `is_available()` neconditionat -> `_get_engine()` ->
    `_load_engine()` -> `FastEmbedBackend()` (incarcare sincrona 30-120s) chiar daca
    `index_corpus` nu a fost apelat niciodata in productie -> corpus gol ->
    `suggest_nearest` ar fi returnat [] oricum (zero beneficiu, cost mare).
    Fix: poarta `has_corpus()` (ieftina, nu construieste engine-ul cand `_engine is None`).
    """
    import app.embeddings as emb_mod

    # Engine ne-initializat -> corpus gol prin definitie.
    monkeypatch.setattr(emb_mod, "_engine", None, raising=False)
    incarcari = {"n": 0}
    orig_load = emb_mod._load_engine

    def _spy_load():
        incarcari["n"] += 1
        return orig_load()

    monkeypatch.setattr(emb_mod, "_load_engine", _spy_load)

    from app.mapping import enrich_suggestions
    result = enrich_suggestions(conn, "Operatie oarecare", include_embeddings=True)

    assert result["surse"]["embedding"] is None
    assert incarcari["n"] == 0, (
        "Modelul de embeddings NU trebuie incarcat cand corpus-ul e gol "
        f"(index_corpus nu e wired). _load_engine apelat de {incarcari['n']} ori."
    )


class _FakeEmbedBackend:
    """Backend embedding determinist (3 dimensiuni keyword) — fara model real 230MB."""

    def embed(self, texts):
        out = []
        for t in texts:
            tl = str(t).lower()
            out.append([
                1.0 if "ulei" in tl else 0.0,
                1.0 if "motor" in tl else 0.0,
                1.0 if "frana" in tl else 0.0,
            ])
        return out


def test_embeddings_functional_cand_flag_activ(conn, monkeypatch):
    """PRD #15: cu AUTOPASS_EMBEDDINGS_ENABLED=true, embeddings produce efectiv o sugestie.

    Wire-uieste ensure_embeddings_corpus (corpus din nomenclator) + enrich_suggestions.
    Backend injectat (determinist) -> nu incarca modelul real de 230MB.
    """
    import app.embeddings as emb_mod
    from app.embeddings import EmbeddingEngine
    from app.config import get_settings

    # Activeaza flagul + injecteaza backend fals in singleton-ul global.
    monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "true")
    get_settings.cache_clear()
    monkeypatch.setattr(emb_mod, "_engine", EmbeddingEngine(backend=_FakeEmbedBackend()))

    # Corpusul sursa = mapping_suggestions (SILVER) -- PRD 5.18 US-005.
    # (Inainte era nomenclator_rar; migrat la mapping_suggestions ca k-NN sa
    # opereze pe exemple reale etichetate, nu pe categorii generice RAR.)
    conn.execute(
        "INSERT OR REPLACE INTO mapping_suggestions "
        "(denumire_normalizata, cod_prestatie, is_nul, source, confidence) VALUES (?, ?, ?, ?, ?)",
        ("Schimb ulei", "UL-1", 0, "llm", 0.95),
    )
    conn.execute(
        "INSERT OR REPLACE INTO mapping_suggestions "
        "(denumire_normalizata, cod_prestatie, is_nul, source, confidence) VALUES (?, ?, ?, ?, ?)",
        ("Placute frana", "FR-1", 0, "llm", 0.95),
    )
    conn.commit()

    from app.mapping import ensure_embeddings_corpus, enrich_suggestions

    ensure_embeddings_corpus(conn)
    assert emb_mod.has_corpus(), "corpusul trebuie indexat cand flagul e activ"

    # "schimbat uleiul motor" -> vector [1,1,0] -> cel mai apropiat = UL-1 (Schimb ulei).
    result = enrich_suggestions(conn, "schimbat uleiul motor", include_embeddings=True)
    assert result["surse"]["embedding"] == "UL-1", (
        f"embeddings trebuie sa sugereze UL-1, got {result['surse']}"
    )

    get_settings.cache_clear()


def test_embeddings_flag_off_ramane_noop(conn, monkeypatch):
    """Cu flagul off (default), ensure_embeddings_corpus e no-op total (nu indexeaza)."""
    import app.embeddings as emb_mod
    from app.embeddings import EmbeddingEngine
    from app.config import get_settings

    monkeypatch.setenv("AUTOPASS_EMBEDDINGS_ENABLED", "false")
    get_settings.cache_clear()
    # Engine cu backend disponibil, dar flagul off -> NU se indexeaza nimic.
    monkeypatch.setattr(emb_mod, "_engine", EmbeddingEngine(backend=_FakeEmbedBackend()))

    from app.mapping import ensure_embeddings_corpus
    ensure_embeddings_corpus(conn)
    assert not emb_mod.has_corpus(), "flag off -> corpusul NU trebuie indexat"

    get_settings.cache_clear()


def test_enrich_silver_nul_ignorat(conn):
    """SILVER cu is_nul=1 (non-operatie) NU apare ca sugestie."""
    from app.shared_store import seed_suggestions
    from app.mapping import enrich_suggestions

    seed_suggestions(conn, [
        {"denumire": "ITP CT 12 ABC", "is_nul": True, "source": "llm", "confidence": 0.99},
    ])
    conn.commit()

    result = enrich_suggestions(conn, "ITP CT 12 ABC")
    assert result["sugestie_principala"] is None
    assert result["surse"]["silver"] is None


# --------------------------------------------------------------------------- #
# pending_unmapped: include sugestie_principala                               #
# --------------------------------------------------------------------------- #

def test_pending_unmapped_include_sugestie_principala(conn):
    """pending_unmapped returneaza entries cu sugestie_principala din GOLD/SILVER."""
    from app.shared_store import record_human_validation
    from app.mapping import pending_unmapped
    import json

    record_human_validation(conn, "Schimb ulei motor", "OE-3")
    conn.commit()

    # Creeaza un submission needs_mapping cu "Schimb ulei motor"
    conn.execute(
        "INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
        "VALUES (1, 'needs_mapping', ?, 'key-test-001')",
        (json.dumps({
            "vin": "WVWZZZ1KZAW001111",
            "prestatii": [{"cod_op_service": "OP-ULEI", "denumire": "Schimb ulei motor"}],
        }),),
    )
    conn.commit()

    pending = pending_unmapped(conn, account_id=1)
    assert len(pending) == 1
    entry = pending[0]

    # sugestie_principala adaugat de enrich_suggestions (L14-S6)
    assert "sugestie_principala" in entry
    sp = entry["sugestie_principala"]
    assert sp is not None
    assert sp["cod_prestatie"] == "OE-3"
    assert sp["sursa"] == "gold_partajat"


def test_pending_unmapped_fara_surse_sugestie_principala_none(conn, monkeypatch):
    """pending_unmapped -> sugestie_principala = None cand nu exista nicio sursa.

    Dezactiveaza embeddings prin poarta reala `has_corpus`=False (gate-ul folosit de
    enrich_suggestions dupa wiring), independent de starea singleton-ului global lasata
    de alte teste (izolare de ordine).
    """
    import app.embeddings as emb_mod
    monkeypatch.setattr(emb_mod, "has_corpus", lambda: False)
    monkeypatch.setattr(emb_mod, "is_available", lambda: False)

    from app.mapping import pending_unmapped
    import json

    conn.execute(
        "INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
        "VALUES (1, 'needs_mapping', ?, 'key-test-002')",
        (json.dumps({
            "vin": "WVWZZZ1KZAW002222",
            "prestatii": [{"cod_op_service": "OP-FARA-SURSA", "denumire": "Operatie de nisa"}],
        }),),
    )
    conn.commit()

    pending = pending_unmapped(conn, account_id=1)
    assert len(pending) == 1
    entry = pending[0]

    assert "sugestie_principala" in entry
    assert entry["sugestie_principala"] is None


# --------------------------------------------------------------------------- #
# record_human_validation apelat la confirmare umana                          #
# --------------------------------------------------------------------------- #

def test_record_human_validation_la_post_mapari(env, client):
    """POST /mapari (tab Mapari) -> record_human_validation scrie in shared_mappings.

    Testul verifica ca GOLD partajat se populeaza automat la confirmarea umana
    din interfata de mapari.
    """
    from app.db import get_connection
    import json

    # Creeaza un submission needs_mapping
    conn_setup = get_connection()
    try:
        conn_setup.executemany(
            "INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
            [("OE-3", "REVIZIE PERIODICA"), ("OE-1", "REPARATIE")],
        )
        conn_setup.execute(
            "INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
            "VALUES (1, 'needs_mapping', ?, 'key-hv-001')",
            (json.dumps({
                "vin": "WVWZZZ1KZAW003333",
                "prestatii": [{"cod_op_service": "OP-REV", "denumire": "Revizie anuala"}],
            }),),
        )
        conn_setup.commit()
    finally:
        conn_setup.close()

    # POST /mapari cu denumire (L14-S6: form include denumire hidden)
    resp = client.post(
        "/mapari",
        data={
            "cod_op_service": "OP-REV",
            "cod_prestatie": "OE-3",
            "denumire": "Revizie anuala",
            "csrf_token": "",
        },
    )
    assert resp.status_code == 200, resp.text

    # Verifica ca shared_mappings contine intrarea
    conn_check = get_connection()
    try:
        from app.shared_store import lookup_shared_gold
        row = lookup_shared_gold(conn_check, "Revizie anuala")
        assert row is not None, "record_human_validation nu a scris in shared_mappings"
        assert row["cod_prestatie"] == "OE-3"
    finally:
        conn_check.close()


def test_record_human_validation_la_mapeaza_inline(env, client):
    """POST /trimitere/{id}/mapeaza -> record_human_validation scrie in shared_mappings.

    Testul verifica ca GOLD partajat se populeaza la maparea inline din panoul de detaliu.
    """
    from app.db import get_connection
    import json

    # Setup submission needs_mapping
    conn_setup = get_connection()
    try:
        conn_setup.executemany(
            "INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
            [("OE-1", "REPARATIE"), ("OE-3", "REVIZIE")],
        )
        conn_setup.execute(
            "INSERT INTO submissions (account_id, status, payload_json, idempotency_key) "
            "VALUES (1, 'needs_mapping', ?, 'key-inline-001')",
            (json.dumps({
                "vin": "WVWZZZ1KZAW004444",
                "data_prestatie": "2026-06-15",
                "odometru_final": 100000,
                "prestatii": [{"cod_op_service": "OP-REP", "denumire": "Reparatie chiulasa"}],
            }),),
        )
        conn_setup.commit()

        # Preia ID-ul submission-ului
        sid = conn_setup.execute("SELECT id FROM submissions WHERE idempotency_key='key-inline-001'").fetchone()["id"]
    finally:
        conn_setup.close()

    resp = client.post(
        f"/trimitere/{sid}/mapeaza",
        data={
            "cod_op_service": "OP-REP",
            "cod_prestatie": "OE-1",
            "csrf_token": "",
        },
    )
    assert resp.status_code == 200, resp.text

    # Verifica shared_mappings
    conn_check = get_connection()
    try:
        from app.shared_store import lookup_shared_gold
        row = lookup_shared_gold(conn_check, "Reparatie chiulasa")
        assert row is not None, "record_human_validation nu a scris in shared_mappings pentru mapeaza inline"
        assert row["cod_prestatie"] == "OE-1"
    finally:
        conn_check.close()


def test_mapare_salvata_fara_denumire_nu_polueaza_gold(env, client):
    """Bug fix (code-review 5.15): editarea unei mapari salvate FARA denumire NU scrie
    o intrare bogus in GOLD partajat (cheiata pe cod_op_service in loc de denumire umana).

    Formularul din _mapari.html nu trimite denumire; vechiul fallback `denumire or
    cod_op_service` scria shared_mappings cheiat pe cod_op_service -> lookup_shared_gold
    (pe denumirea umana) nu il potrivea niciodata -> poluare. Fix: _record_gold_validation
    sare scrierea cand denumire lipseste sau == cod_op_service.
    """
    from app.db import get_connection

    conn_setup = get_connection()
    try:
        conn_setup.execute(
            "INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
            ("OE-1", "REPARATIE"),
        )
        conn_setup.commit()
    finally:
        conn_setup.close()

    # Editare mapare salvata FARA denumire (ca formularul real din _mapari.html).
    resp = client.post(
        "/mapari/salvate",
        data={
            "cod_op_service": "OP-SALV",
            "cod_prestatie": "OE-1",
            "csrf_token": "",
        },
    )
    assert resp.status_code == 200, resp.text

    conn_check = get_connection()
    try:
        from app.shared_store import lookup_shared_gold
        # NICIO intrare bogus cheiata pe cod_op_service.
        assert lookup_shared_gold(conn_check, "OP-SALV") is None, (
            "GOLD partajat poluat cu cod_op_service ca si cheie (denumire lipsa)"
        )
    finally:
        conn_check.close()


# --------------------------------------------------------------------------- #
# Separare structurala #13 (redundant cu test_shared_store dar explicit L14)  #
# --------------------------------------------------------------------------- #

def test_separare_silver_din_resolve_prestatii():
    """#13: resolve_prestatii nu citeste mapping_suggestions (SILVER)."""
    from app.mapping import resolve_prestatii

    # Apelam fara conn (pur) — SILVER nu e parametru si nu e accesat
    resolved, unmapped = resolve_prestatii(
        [{"cod_op_service": "OP-TEST", "denumire": "Test silver"}],
        {},  # mapping gol
    )
    assert resolved[0]["cod_prestatie"] is None
    assert len(unmapped) == 1


def test_separare_shared_gold_din_resolve_prestatii():
    """#13: resolve_prestatii nu citeste shared_mappings (GOLD partajat)."""
    from app.mapping import resolve_prestatii

    resolved, unmapped = resolve_prestatii(
        [{"cod_op_service": "OP-TEST2", "denumire": "Test gold partajat"}],
        {},  # mapping gol
    )
    assert resolved[0]["cod_prestatie"] is None
    assert len(unmapped) == 1
