import json
from pathlib import Path

from app.services.code_kb.adapter import CodeKnowledgeBaseAdapter
from app.services.code_kb.graph import CodeCallGraph
from app.services.code_kb.schema import CodeFunctionEvidence, CodeSearchHit
from app.services.consistency.comparator import ConsistencyComparator
from app.services.consistency.schema import RequirementSnapshot
from app.services.consistency.scorer import coverage_score


def test_metadata_normalization_uses_aliases_and_graph_fields():
    adapter = CodeKnowledgeBaseAdapter()
    graph_nodes = {
        "Function:Init": {
            "id": "Function:Init",
            "name": "Init",
            "file_path": "src/init.c",
            "start_line": 10,
            "end_line": 20,
            "signature": "void Init(void)",
            "logic_flow": "init flow",
            "raw_attributes": {"code_snippet": "void Init(void) {}", "includes": ["a.h"]},
        }
    }
    evidence = adapter._normalize_metadata(
        {"node_id": "Function:Init", "function_name": "Init", "file_path": "src/init.c", "summary": "summary"},
        graph_nodes,
        index_dimension=1024,
    )

    assert evidence.name == "Init"
    assert evidence.file == "src/init.c"
    assert evidence.start_line == 10
    assert evidence.code_snippet == "void Init(void) {}"
    assert evidence.embedding_dim == 1024


def test_call_graph_expands_metadata_edges():
    center = CodeFunctionEvidence(node_id="Function:Center", name="Center", qualified_name="Center", file="a.c")
    caller = CodeFunctionEvidence(
        node_id="Function:Caller",
        name="Caller",
        qualified_name="Caller",
        file="a.c",
        calls=["Center"],
    )
    callee = CodeFunctionEvidence(node_id="Function:Callee", name="Callee", qualified_name="Callee", file="a.c")
    center.calls = ["Callee"]
    graph = CodeCallGraph({}, [center, caller, callee])

    context = graph.expand("Function:Center", max_hops=1)

    assert [item.name for item in context.callers] == ["Caller"]
    assert [item.name for item in context.callees] == ["Callee"]
    assert "Caller -> Center -> Callee" in context.call_chains


def test_simple_vector_index_json_can_be_loaded():
    vector_path = Path("uploads") / "test-simple-vector-index.faiss"
    vector_path.parent.mkdir(parents=True, exist_ok=True)
    try:
        vector_path.write_text(
            json.dumps(
                {
                    "format": "simple_l2_vector_index",
                    "dimension": 3,
                    "vectors": [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]],
                }
            ),
            encoding="utf-8",
        )

        adapter = CodeKnowledgeBaseAdapter()
        index = adapter._read_faiss_index(str(vector_path))

        assert index.ntotal == 2
        assert index.d == 3
    finally:
        if vector_path.exists():
            vector_path.unlink()


def test_lexical_search_falls_back_to_function_metadata():
    adapter = CodeKnowledgeBaseAdapter()
    adapter.functions = [
        CodeFunctionEvidence(
            node_id="Function:InitAttEnv",
            name="InitAttEnv",
            qualified_name="InitAttEnv",
            file="AttEnvMod.c",
            summary="initializes attitude environment parameters",
            code_snippet="void InitAttEnv(void) {}",
        ),
        CodeFunctionEvidence(
            node_id="Function:MatrixProduct",
            name="MatrixProduct",
            qualified_name="MatrixProduct",
            file="AttMath.c",
            summary="matrix multiplication",
        ),
    ]

    hits = adapter._lexical_search_functions("InitAttEnv attitude environment", top_k=1, min_similarity=0.2)

    assert hits[0].evidence.name == "InitAttEnv"


def test_parse_json_judgment_from_markdown_block():
    raw = """```json
{"verdict":"partial","confidence":0.6,"covered_points":["A"],"missing_points":["B"]}
```"""

    parsed = ConsistencyComparator.parse_json_judgment(raw)

    assert parsed["verdict"] == "partial"
    assert parsed["confidence"] == 0.6


def test_coverage_score_caps_missing_verdict():
    requirement = RequirementSnapshot(
        requirement_uid="REQ-1",
        title="",
        description="init system",
        acceptance_criteria=["A", "B"],
    )
    evidence = CodeFunctionEvidence(
        node_id="Function:Init",
        name="Init",
        qualified_name="Init",
        file="init.c",
        start_line=1,
        end_line=10,
        summary="init system",
        logic_flow="flow",
        code_snippet="code",
    )
    score = coverage_score(
        requirement,
        [CodeSearchHit(evidence=evidence, similarity=0.95, distance=0.1, rank=1)],
        [],
        {"verdict": "missing", "missing_points": ["B"]},
    )

    assert score <= 0.25
