"""
Architecture Guard — tests.

Covers:
  - No config → GuardResult with 0 violations (graceful)
  - Clean graph (no violations)
  - Violation detected (api imports repository directly)
  - Auto-detect layers from directory structure
  - ci-check exit code via CLI runner
"""

import textwrap
import tempfile
from pathlib import Path

import pytest

from kodara.architecture.guard import (
    check,
    generate_config,
    load_config,
    _match_layer,
    GuardResult,
    Violation,
)
from kodara.memory.store import KodaraIndex


# ── Helpers ────────────────────────────────────────────────────────────────────

def _make_index(edges: list[dict]) -> KodaraIndex:
    """Build a minimal KodaraIndex with the given graph edges."""
    modules = {}
    for edge in edges:
        modules.setdefault(edge["from"], {"module_name": Path(edge["from"]).stem})
        modules.setdefault(edge["to"],   {"module_name": Path(edge["to"]).stem})

    return KodaraIndex(
        repo_id="test/proj",
        display_name="test-proj",
        source_root="/tmp/proj",
        indexed_at=0.0,
        index_version=2,
        file_count=len(modules),
        language_counts={"python": len(modules)},
        modules=modules,
        graph_edges=edges,
        file_hashes={},
    )


def _write_config(kodara_dir: Path, content: str) -> None:
    """Write architecture.yml to .kodara/."""
    kodara_dir.mkdir(parents=True, exist_ok=True)
    (kodara_dir / "architecture.yml").write_text(content, encoding="utf-8")


# ── Tests: no config ───────────────────────────────────────────────────────────

def test_no_config_returns_empty_result(tmp_path):
    """Without architecture.yml, check() returns a clean result — no crash."""
    kodara_dir = tmp_path / ".kodara"
    kodara_dir.mkdir()

    idx = _make_index([{"from": "api/routes.py", "to": "models/user.py", "kind": "import"}])
    result = check(idx, kodara_dir)

    assert result.is_clean
    assert result.violations == []
    assert result.checked_edges == 0


# ── Tests: clean graph ─────────────────────────────────────────────────────────

def test_clean_graph_no_violations(tmp_path):
    """api → service → repository — all valid, no violations."""
    kodara_dir = tmp_path / ".kodara"
    _write_config(kodara_dir, textwrap.dedent("""\
        architecture:
          api:
            patterns: ["api/*"]
            can_import: [service]
          service:
            patterns: ["service/*"]
            can_import: [repository]
          repository:
            patterns: ["repository/*"]
            can_import: []
    """))

    edges = [
        {"from": "api/routes.py",         "to": "service/user_service.py", "kind": "import"},
        {"from": "service/user_service.py", "to": "repository/user_repo.py",  "kind": "import"},
    ]
    idx = _make_index(edges)
    result = check(idx, kodara_dir)

    assert result.is_clean
    assert result.checked_edges == 2


# ── Tests: violations ─────────────────────────────────────────────────────────

def test_api_imports_repository_is_violation(tmp_path):
    """api directly importing repository violates layer rules."""
    kodara_dir = tmp_path / ".kodara"
    _write_config(kodara_dir, textwrap.dedent("""\
        architecture:
          api:
            patterns: ["api/*"]
            can_import: [service]
          service:
            patterns: ["service/*"]
            can_import: [repository]
          repository:
            patterns: ["repository/*"]
            can_import: []
    """))

    edges = [
        {"from": "api/routes.py", "to": "repository/user_repo.py", "kind": "import"},
    ]
    idx = _make_index(edges)
    result = check(idx, kodara_dir)

    assert not result.is_clean
    assert len(result.violations) == 1
    v = result.violations[0]
    assert v.from_layer == "api"
    assert v.to_layer == "repository"
    assert "api cannot import repository" in v.rule


def test_multiple_violations_detected(tmp_path):
    """Multiple rule breaks all show up."""
    kodara_dir = tmp_path / ".kodara"
    _write_config(kodara_dir, textwrap.dedent("""\
        architecture:
          api:
            patterns: ["api/*"]
            can_import: [service]
          service:
            patterns: ["service/*"]
            can_import: [repository]
          repository:
            patterns: ["repository/*"]
            can_import: []
    """))

    edges = [
        {"from": "api/routes.py",   "to": "repository/repo.py",   "kind": "import"},
        {"from": "repository/repo.py", "to": "service/svc.py",    "kind": "import"},
    ]
    idx = _make_index(edges)
    result = check(idx, kodara_dir)

    assert len(result.violations) == 2


def test_same_layer_import_allowed(tmp_path):
    """Two files in the same layer importing each other is always ok."""
    kodara_dir = tmp_path / ".kodara"
    _write_config(kodara_dir, textwrap.dedent("""\
        architecture:
          service:
            patterns: ["service/*"]
            can_import: []
    """))

    edges = [
        {"from": "service/auth.py", "to": "service/token.py", "kind": "import"},
    ]
    idx = _make_index(edges)
    result = check(idx, kodara_dir)

    assert result.is_clean


def test_unlayered_file_not_flagged(tmp_path):
    """Files that don't match any layer pattern are skipped — no false positives."""
    kodara_dir = tmp_path / ".kodara"
    _write_config(kodara_dir, textwrap.dedent("""\
        architecture:
          api:
            patterns: ["api/*"]
            can_import: [service]
          service:
            patterns: ["service/*"]
            can_import: []
    """))

    # conftest.py doesn't match any layer
    edges = [
        {"from": "conftest.py", "to": "api/routes.py", "kind": "import"},
    ]
    idx = _make_index(edges)
    result = check(idx, kodara_dir)

    assert result.is_clean


# ── Tests: layer matching ──────────────────────────────────────────────────────

def test_match_layer_direct():
    layers = {
        "api": {"patterns": ["api/*"]},
        "service": {"patterns": ["service/*"]},
    }
    assert _match_layer("api/routes.py", layers) == "api"
    assert _match_layer("service/auth.py", layers) == "service"
    assert _match_layer("main.py", layers) is None


def test_match_layer_nested_path():
    """Pattern api/* should match src/api/routes.py via suffix matching."""
    layers = {"api": {"patterns": ["api/*"]}}
    assert _match_layer("src/api/routes.py", layers) == "api"


# ── Tests: auto-detect ────────────────────────────────────────────────────────

def test_generate_config_detects_layers(tmp_path):
    """arch-init detects api/ and service/ directories."""
    (tmp_path / "api").mkdir()
    (tmp_path / "service").mkdir()
    (tmp_path / "README.md").write_text("hi")

    kodara_dir = tmp_path / ".kodara"
    kodara_dir.mkdir()

    config, config_path = generate_config(tmp_path, kodara_dir)
    layers = config.get("architecture", {})

    assert "api" in layers
    assert "service" in layers
    assert "repository" not in layers   # not present in this project


def test_generate_config_no_dirs(tmp_path):
    """If no standard dirs exist, empty architecture dict is returned."""
    kodara_dir = tmp_path / ".kodara"
    kodara_dir.mkdir()

    config, _ = generate_config(tmp_path, kodara_dir)
    assert config.get("architecture", {}) == {}


def test_generate_config_prunes_can_import(tmp_path):
    """can_import only references layers that were actually detected."""
    (tmp_path / "api").mkdir()
    # service is NOT present — api.can_import should NOT include it

    kodara_dir = tmp_path / ".kodara"
    kodara_dir.mkdir()

    config, _ = generate_config(tmp_path, kodara_dir)
    api_cfg = config.get("architecture", {}).get("api", {})
    # "service" must not appear in can_import since it wasn't detected
    assert "service" not in api_cfg.get("can_import", [])


# ── Regression: existing commands unaffected ─────────────────────────────────

def test_existing_imports_still_work():
    """Core modules still import cleanly — no regressions."""
    from kodara.memory.store import MemoryStore, KodaraIndex
    from kodara.graph.dependency import DependencyGraph
    from kodara.engine_client import query_context, query_impact
    from kodara.architecture.guard import check, generate_config
    # If we reach here without ImportError — all good
    assert True


# ── CLI: ci-check exit codes ──────────────────────────────────────────────────

def test_ci_check_exit_0_no_config(tmp_path):
    """ci-check exits 0 when no architecture.yml exists."""
    from click.testing import CliRunner
    from kodara.cli.main import ci_check

    # Create a minimal .kodara dir with index
    kodara_dir = tmp_path / ".kodara"
    kodara_dir.mkdir()

    runner = CliRunner()
    result = runner.invoke(ci_check, [str(tmp_path)])
    # No index → exits 0 (graceful skip)
    assert result.exit_code == 0


def test_ci_check_exit_1_on_violations(tmp_path):
    """ci-check exits 1 when violations exist."""
    import yaml
    from click.testing import CliRunner
    from kodara.cli.main import ci_check
    from kodara.memory.store import MemoryStore

    # Write a real index
    kodara_dir = tmp_path / ".kodara"
    kodara_dir.mkdir()

    idx = _make_index([
        {"from": "api/routes.py", "to": "repository/repo.py", "kind": "import"},
    ])

    store = MemoryStore(tmp_path)
    store.save(idx)

    # Write config with a rule that will be violated
    config = {
        "architecture": {
            "api":        {"patterns": ["api/*"],        "can_import": ["service"]},
            "repository": {"patterns": ["repository/*"], "can_import": []},
        }
    }
    with open(kodara_dir / "architecture.yml", "w") as f:
        yaml.dump(config, f)

    runner = CliRunner()
    result = runner.invoke(ci_check, [str(tmp_path)])
    assert result.exit_code == 1
