"""Tests para build_obsidian_graph.

Construyen mini-vaults temporales con notas en personas/ y organizaciones/,
wikilinks entre ellas (incluido uno roto y uno con acentos) y comprueban el
grafo resultante: numero de nodos/aristas, resolucion de wikilinks acentuados,
marcado de dangling y robustez ante enlaces rotos.
"""

import os
import tempfile

from obsidian.build_obsidian_graph import build_obsidian_graph


def _write(vault: str, rel_path: str, content: str) -> None:
    """Escribe una nota .md en el mini-vault, creando carpetas si hace falta."""
    full = os.path.join(vault, rel_path)
    os.makedirs(os.path.dirname(full), exist_ok=True)
    with open(full, "w", encoding="utf-8") as f:
        f.write(content)


def _build_sample_vault(vault: str) -> None:
    """Crea un vault de prueba con 4 notas reales + 1 wikilink dangling.

    Grafo esperado (aristas dirigidas):
        ana    -> bruno         (## Relaciones, kind relacion)
        ana    -> maria-del-mar (## Relaciones, kind relacion, acento)
        ana    -> acme-sl       (## Documentos, kind documento)
        bruno  -> persona-fantasma (## Relaciones, NO existe -> dangling)
        acme-sl-> ana           (cuerpo suelto, kind wikilink)
    """
    # .obsidian/ debe ignorarse por completo.
    _write(vault, ".obsidian/app.json", "{}")

    _write(
        vault,
        "personas/ana.md",
        "---\n"
        "tipo: persona\n"
        "nombre: Ana Gómez\n"
        "---\n\n"
        "## Relaciones\n"
        "- [[bruno]] — amigo\n"
        "- [[María del Mar]] — vecina\n\n"
        "## Documentos\n"
        "- [[organizaciones/acme-sl|Acme SL]]\n",
    )
    _write(
        vault,
        "personas/bruno.md",
        "---\n"
        "tipo: persona\n"
        "nombre: Bruno Ruiz\n"
        "---\n\n"
        "## Relaciones\n"
        "- [[Persona Fantasma]] — desconocido\n",
    )
    # Nota con acento en el nombre de archivo cuyo slug es maria-del-mar.
    _write(
        vault,
        "personas/maria-del-mar.md",
        "---\n"
        "tipo: persona\n"
        "nombre: María del Mar\n"
        "---\n\n"
        "## Notas\n"
        "Sin enlaces.\n",
    )
    _write(
        vault,
        "organizaciones/acme-sl.md",
        "---\n"
        "tipo: organizacion\n"
        "nombre: Acme SL\n"
        "---\n\n"
        "Cliente de [[ana]].\n",
    )


def test_golden_graph_node_and_edge_counts():
    """Golden: el grafo del mini-vault tiene los nodos y aristas esperados."""
    with tempfile.TemporaryDirectory() as vault:
        _build_sample_vault(vault)
        graph = build_obsidian_graph(vault, include_dangling=True)

        real = [n for n in graph["nodes"] if not n.get("dangling")]
        dangling = [n for n in graph["nodes"] if n.get("dangling")]

        # 4 notas reales + 1 nodo fantasma (persona-fantasma).
        assert len(real) == 4, [n["id"] for n in real]
        assert len(dangling) == 1, [n["id"] for n in dangling]

        # 5 aristas: ana->bruno, ana->maria-del-mar, ana->acme-sl,
        # bruno->persona-fantasma, acme-sl->ana.
        assert len(graph["edges"]) == 5, graph["edges"]

        ids = {n["id"] for n in real}
        assert ids == {"ana", "bruno", "maria-del-mar", "acme-sl"}, ids

        # El tipo y el label salen del frontmatter.
        ana = next(n for n in real if n["id"] == "ana")
        assert ana["tipo"] == "persona"
        assert ana["label"] == "Ana Gómez"
        assert ana["frontmatter"]["nombre"] == "Ana Gómez"


def test_edge_resolves_wikilink_with_accents():
    """Edge: [[María del Mar]] resuelve al nodo slug maria-del-mar."""
    with tempfile.TemporaryDirectory() as vault:
        _build_sample_vault(vault)
        graph = build_obsidian_graph(vault, include_dangling=True)

        edge = next(
            (e for e in graph["edges"] if e["source"] == "ana" and e["target"] == "maria-del-mar"),
            None,
        )
        assert edge is not None, graph["edges"]
        assert edge["kind"] == "relacion", edge


def test_edge_kind_from_section():
    """Edge: el kind de la arista se deduce de la seccion ## donde aparece."""
    with tempfile.TemporaryDirectory() as vault:
        _build_sample_vault(vault)
        graph = build_obsidian_graph(vault, include_dangling=True)

        kinds = {(e["source"], e["target"]): e["kind"] for e in graph["edges"]}
        assert kinds[("ana", "bruno")] == "relacion", kinds
        assert kinds[("ana", "acme-sl")] == "documento", kinds
        # acme-sl -> ana esta fuera de cualquier seccion -> wikilink por defecto.
        assert kinds[("acme-sl", "ana")] == "wikilink", kinds


def test_edge_dangling_marked_and_excluded():
    """Edge: dangling=True crea nodo fantasma; dangling=False lo descarta."""
    with tempfile.TemporaryDirectory() as vault:
        _build_sample_vault(vault)

        with_dangling = build_obsidian_graph(vault, include_dangling=True)
        ghost = next(
            (n for n in with_dangling["nodes"] if n["id"] == "persona-fantasma"),
            None,
        )
        assert ghost is not None and ghost.get("dangling") is True, with_dangling["nodes"]
        assert ghost["tipo"] == "desconocido", ghost
        # La arista hacia el fantasma existe.
        assert any(
            e["target"] == "persona-fantasma" for e in with_dangling["edges"]
        ), with_dangling["edges"]

        without_dangling = build_obsidian_graph(vault, include_dangling=False)
        assert all(
            n["id"] != "persona-fantasma" for n in without_dangling["nodes"]
        ), without_dangling["nodes"]
        # La arista rota se descarta junto con el nodo fantasma.
        assert all(
            e["target"] != "persona-fantasma" for e in without_dangling["edges"]
        ), without_dangling["edges"]
        # Pasamos de 5 a 4 aristas (se elimina bruno->persona-fantasma).
        assert len(without_dangling["edges"]) == 4, without_dangling["edges"]


def test_edge_tipo_falls_back_to_folder():
    """Edge: sin campo 'tipo' en frontmatter, el tipo sale de la carpeta."""
    with tempfile.TemporaryDirectory() as vault:
        _write(vault, "personas/sin-tipo.md", "---\nnombre: Sin Tipo\n---\n\nCuerpo.\n")
        _write(vault, "organizaciones/org-sin-tipo.md", "Sin frontmatter.\n")

        graph = build_obsidian_graph(vault)
        by_id = {n["id"]: n for n in graph["nodes"]}
        assert by_id["sin-tipo"]["tipo"] == "persona", by_id["sin-tipo"]
        assert by_id["org-sin-tipo"]["tipo"] == "organizacion", by_id["org-sin-tipo"]
        # Sin frontmatter ni nombre, el label cae al slug.
        assert by_id["org-sin-tipo"]["label"] == "org-sin-tipo", by_id["org-sin-tipo"]


def test_error_path_broken_wikilink_no_crash():
    """Error path: un wikilink sintacticamente roto no tumba el grafo."""
    with tempfile.TemporaryDirectory() as vault:
        # Wikilink sin cerrar, doble corchete suelto y wikilink vacio.
        _write(
            vault,
            "personas/raro.md",
            "---\ntipo: persona\nnombre: Raro\n---\n\n"
            "Texto con [[ roto y [[ ]] vacio y [[bien]] valido.\n",
        )
        _write(vault, "personas/bien.md", "---\ntipo: persona\nnombre: Bien\n---\n\nOK\n")

        graph = build_obsidian_graph(vault, include_dangling=True)
        # No crash; el unico enlace valido se resuelve a 'bien'.
        edges = [(e["source"], e["target"]) for e in graph["edges"]]
        assert ("raro", "bien") in edges, edges
        # El wikilink vacio no genera arista ni nodo fantasma vacio.
        assert all(n["id"] for n in graph["nodes"]), graph["nodes"]


def test_error_path_missing_vault_raises():
    """Error path: un vault inexistente lanza FileNotFoundError, no 500 mudo."""
    raised = False
    try:
        build_obsidian_graph("/no/existe/vault/osint")
    except FileNotFoundError:
        raised = True
    assert raised, "build_obsidian_graph deberia lanzar FileNotFoundError"


if __name__ == "__main__":
    test_golden_graph_node_and_edge_counts()
    test_edge_resolves_wikilink_with_accents()
    test_edge_kind_from_section()
    test_edge_dangling_marked_and_excluded()
    test_edge_tipo_falls_back_to_folder()
    test_error_path_broken_wikilink_no_crash()
    test_error_path_missing_vault_raises()
    print("build_obsidian_graph tests OK")
