"""Tests for the Pascal/Delphi extractor."""
from __future__ import annotations
from pathlib import Path

FIXTURES = Path(__file__).parent / "fixtures"


def _labels(r):
    return [n["label"] for n in r["nodes"]]


def _relations(r):
    return {e["relation"] for e in r["edges"]}


def _edges_with_relation(r, *relations):
    return [e for e in r["edges"] if e["relation"] in relations]


def test_pascal_no_error():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert "error" not in r


def test_pascal_finds_unit():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert any("SampleUnit" in l for l in _labels(r))


def test_pascal_finds_classes():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    labels = _labels(r)
    assert any("TBaseProcessor" in l for l in labels)
    assert any("TDataProcessor" in l for l in labels)


def test_pascal_finds_interface():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert any("IProcessor" in l for l in _labels(r))


def test_pascal_finds_methods():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    labels = _labels(r)
    assert any("Process" in l for l in labels)
    assert any("Initialize" in l for l in labels)
    assert any("GetCount" in l for l in labels)
    assert any("Reset" in l for l in labels)


def test_pascal_finds_imports():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert "imports" in _relations(r)


def test_pascal_import_edges_have_import_context():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    import_edges = _edges_with_relation(r, "imports")
    assert import_edges
    assert all(e.get("context") == "import" for e in import_edges)


def test_pascal_finds_inherits():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert "inherits" in _relations(r)


def test_pascal_inherits_from_base():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
    inherits = [e for e in r["edges"] if e["relation"] == "inherits"]
    found = any(
        "TDataProcessor" in node_by_id.get(e["source"], "")
        for e in inherits
    )
    assert found, "TDataProcessor should have at least one inherits edge"


def test_pascal_finds_calls():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert "calls" in _relations(r)


def test_pascal_call_edges_have_call_context():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    call_edges = _edges_with_relation(r, "calls")
    assert call_edges
    assert all(e.get("context") == "call" for e in call_edges)


def test_pascal_all_edges_extracted():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    structural = {"contains", "method", "inherits", "imports"}
    for e in r["edges"]:
        if e["relation"] in structural:
            assert e["confidence"] == "EXTRACTED", f"Expected EXTRACTED: {e}"


def test_pascal_no_dangling_edges():
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    node_ids = {n["id"] for n in r["nodes"]}
    # imports edges are cross-file by design; only check within-file edge targets
    within_file_relations = {"contains", "method", "inherits", "calls"}
    for e in r["edges"]:
        assert e["source"] in node_ids, f"Dangling source: {e}"
        if e["relation"] in within_file_relations:
            assert e["target"] in node_ids, f"Dangling target: {e}"


def test_pascal_dispatch_registered():
    from graphify.extract import _DISPATCH
    assert ".pas" in _DISPATCH
    assert ".pp" in _DISPATCH
    assert ".dpr" in _DISPATCH
    assert ".dpk" in _DISPATCH
    assert ".lpr" in _DISPATCH
    assert ".inc" in _DISPATCH
    assert ".lfm" in _DISPATCH
    assert ".lpk" in _DISPATCH


def test_pascal_detect_extensions_registered():
    from graphify.detect import CODE_EXTENSIONS
    assert ".pas" in CODE_EXTENSIONS
    assert ".pp" in CODE_EXTENSIONS
    assert ".dpr" in CODE_EXTENSIONS
    assert ".lpr" in CODE_EXTENSIONS
    assert ".lfm" in CODE_EXTENSIONS
    assert ".lpk" in CODE_EXTENSIONS


# ── Lazarus Form (.lfm) ───────────────────────────────────────────────────────

def test_lfm_no_error():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    assert "error" not in r


def test_lfm_finds_root_form_class():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    assert any("TSampleForm" in l for l in _labels(r))


def test_lfm_finds_component_classes():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    labels = _labels(r)
    assert any("TPanel" in l for l in labels)
    assert any("TButton" in l for l in labels)
    assert any("TLabel" in l for l in labels)
    assert any("TTimer" in l for l in labels)


def test_lfm_finds_event_handlers():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    labels = _labels(r)
    assert any("ButtonOKClick" in l for l in labels)
    assert any("TimerRefreshTimer" in l for l in labels)


def test_lfm_event_edges_have_event_context():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    ref_edges = [e for e in r["edges"] if e["relation"] == "references"]
    assert ref_edges
    assert all(e.get("context") == "event" for e in ref_edges)


def test_lfm_contains_edges_form_hierarchy():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    assert "contains" in _relations(r)


def test_lfm_no_dangling_edges():
    from graphify.extract import extract_lazarus_form
    r = extract_lazarus_form(FIXTURES / "sample.lfm")
    node_ids = {n["id"] for n in r["nodes"]}
    for e in r["edges"]:
        assert e["source"] in node_ids, f"Dangling source: {e}"


# ── Lazarus Package (.lpk) ───────────────────────────────────────────────────

def test_lpk_no_error():
    from graphify.extract import extract_lazarus_package
    r = extract_lazarus_package(FIXTURES / "sample.lpk")
    assert "error" not in r


def test_lpk_finds_package_name():
    from graphify.extract import extract_lazarus_package
    r = extract_lazarus_package(FIXTURES / "sample.lpk")
    assert any("SamplePackage" in l for l in _labels(r))


def test_lpk_finds_required_packages():
    from graphify.extract import extract_lazarus_package
    r = extract_lazarus_package(FIXTURES / "sample.lpk")
    labels = _labels(r)
    assert any("FCL" in l for l in labels)
    assert any("LCL" in l for l in labels)


def test_lpk_imports_edges_have_import_context():
    from graphify.extract import extract_lazarus_package
    r = extract_lazarus_package(FIXTURES / "sample.lpk")
    import_edges = _edges_with_relation(r, "imports")
    assert import_edges
    assert all(e.get("context") == "import" for e in import_edges)


def test_lpk_contains_listed_units():
    from graphify.extract import extract_lazarus_package
    r = extract_lazarus_package(FIXTURES / "sample.lpk")
    labels = _labels(r)
    assert any("sample" in l.lower() for l in labels)
    assert any("sampleutils" in l.lower() for l in labels)


def test_lpk_no_dangling_edges():
    from graphify.extract import extract_lazarus_package
    r = extract_lazarus_package(FIXTURES / "sample.lpk")
    node_ids = {n["id"] for n in r["nodes"]}
    for e in r["edges"]:
        assert e["source"] in node_ids, f"Dangling source: {e}"


# ── Delphi Form (.dfm) ───────────────────────────────────────────────────────

def test_dfm_no_error():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    assert "error" not in r


def test_dfm_finds_root_form_class():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    assert any("TMainForm" in l for l in _labels(r))


def test_dfm_finds_component_classes():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    labels = _labels(r)
    assert any("TPanel" in l for l in labels)
    assert any("TButton" in l for l in labels)
    assert any("TMemo" in l for l in labels)
    assert any("TStatusBar" in l for l in labels)


def test_dfm_finds_event_handlers():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    labels = _labels(r)
    assert any("FormCreate" in l for l in labels)
    assert any("ButtonOKClick" in l for l in labels)


def test_dfm_event_edges_have_event_context():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    ref_edges = [e for e in r["edges"] if e["relation"] == "references"]
    assert ref_edges
    assert all(e.get("context") == "event" for e in ref_edges)


def test_dfm_contains_edges_form_hierarchy():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    assert "contains" in _relations(r)


def test_dfm_no_dangling_edges():
    from graphify.extract import extract_delphi_form
    r = extract_delphi_form(FIXTURES / "sample.dfm")
    node_ids = {n["id"] for n in r["nodes"]}
    for e in r["edges"]:
        assert e["source"] in node_ids, f"Dangling source: {e}"


def test_dfm_binary_returns_empty_not_crash():
    from graphify.extract import extract_delphi_form
    import tempfile, pathlib
    # Write a fake binary DFM (FF 0A magic header)
    with tempfile.NamedTemporaryFile(suffix=".dfm", delete=False) as f:
        f.write(b"\xff\x0a\x00\x00some binary data")
        tmp = pathlib.Path(f.name)
    try:
        r = extract_delphi_form(tmp)
        assert r["nodes"] == []
        assert r["edges"] == []
        assert "error" in r
    finally:
        tmp.unlink()


def test_dfm_dispatch_registered():
    from graphify.extract import _DISPATCH
    assert ".dfm" in _DISPATCH


def test_dfm_detect_extension_registered():
    from graphify.detect import CODE_EXTENSIONS
    assert ".dfm" in CODE_EXTENSIONS


def _dup_edges(r):
    from collections import Counter
    triples = Counter((e["source"], e["target"], e["relation"]) for e in r["edges"])
    return {k: v for k, v in triples.items() if v > 1}


def test_pascal_no_duplicate_method_edges_tree_sitter():
    """A class method appears in both the interface declaration and the
    implementation; each used to emit a `method` edge to the same node, so the
    graph carried doubled method/contains/inherits edges (skewing degree and
    breaking the cross-file inherited-call resolver's god-node guard). Edges are
    now deduped on (source, target, relation)."""
    from graphify.extract import extract_pascal
    r = extract_pascal(FIXTURES / "sample.pas")
    assert _dup_edges(r) == {}, f"duplicate edges: {_dup_edges(r)}"


def test_pascal_no_duplicate_method_edges_regex():
    from graphify.extract import _extract_pascal_regex
    r = _extract_pascal_regex(FIXTURES / "sample.pas")
    assert _dup_edges(r) == {}, f"duplicate edges: {_dup_edges(r)}"
