"""
Architecture Guard — validates that import dependencies respect
layer rules defined in .kodara/architecture.yml

Layer config example:

    architecture:
      api:
        patterns: ["api/*", "routes/*", "views/*"]
        can_import: [service, utils]
      service:
        patterns: ["service/*", "services/*"]
        can_import: [repository, utils]
      repository:
        patterns: ["repository/*", "models/*", "db/*"]
        can_import: [utils]
      utils:
        patterns: ["utils/*", "helpers/*", "common/*"]
        can_import: []
"""

from __future__ import annotations

import fnmatch
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

try:
    import yaml
    HAS_YAML = True
except ImportError:
    HAS_YAML = False

from ..memory.store import KodaraIndex


CONFIG_FILE = "architecture.yml"

# Common layer patterns for auto-detection (layer_name → candidate dir names)
AUTO_DETECT_LAYERS: list[tuple[str, list[str]]] = [
    ("api",        ["api/*", "routes/*", "route/*", "views/*", "view/*",
                    "controllers/*", "controller/*", "endpoints/*"]),
    ("service",    ["service/*", "services/*", "use_cases/*",
                    "usecases/*", "business/*"]),
    ("repository", ["repository/*", "repositories/*", "repo/*",
                    "models/*", "model/*", "db/*", "database/*", "dal/*"]),
    ("utils",      ["utils/*", "util/*", "helpers/*", "helper/*",
                    "common/*", "shared/*"]),
]

# Default allowed imports per layer (used during auto-init)
DEFAULT_RULES: dict[str, list[str]] = {
    "api":        ["service", "utils"],
    "service":    ["repository", "utils"],
    "repository": ["utils"],
    "utils":      [],
}


@dataclass
class Violation:
    from_file: str
    to_file: str
    from_layer: str
    to_layer: str
    rule: str   # human-readable: "api cannot import repository"


@dataclass
class GuardResult:
    violations: list[Violation] = field(default_factory=list)
    checked_edges: int = 0
    config_path: str = ""

    @property
    def is_clean(self) -> bool:
        return len(self.violations) == 0


# ── Config loading ─────────────────────────────────────────────────────────────


def load_config(kodara_dir: Path) -> Optional[dict]:
    """
    Load .kodara/architecture.yml.
    Returns the architecture dict, or None if the file doesn't exist.
    Raises ImportError if pyyaml is not installed.
    """
    config_path = kodara_dir / CONFIG_FILE
    if not config_path.exists():
        return None

    if not HAS_YAML:
        raise ImportError(
            "pyyaml is required for architecture checks.\n"
            "Install with: pip install pyyaml"
        )

    with open(config_path, encoding="utf-8") as f:
        data = yaml.safe_load(f)

    if not data or "architecture" not in data:
        return {}

    return data["architecture"]


# ── Layer matching ─────────────────────────────────────────────────────────────


def _match_layer(file_path: str, layers: dict) -> Optional[str]:
    """
    Determine which layer a file belongs to based on glob patterns.
    Tries both full path and each path suffix (so "api/*" matches "src/api/x.py").
    Returns the layer name or None if no match.
    """
    norm = file_path.replace("\\", "/")
    parts = norm.split("/")

    for layer_name, layer_cfg in layers.items():
        patterns = layer_cfg.get("patterns", [])
        for pattern in patterns:
            # Direct match
            if fnmatch.fnmatch(norm, pattern):
                return layer_name
            # Suffix match: try each sub-path
            for i in range(len(parts)):
                sub = "/".join(parts[i:])
                if fnmatch.fnmatch(sub, pattern):
                    return layer_name

    return None


# ── Main validation ────────────────────────────────────────────────────────────


def check(index: KodaraIndex, kodara_dir: Path) -> GuardResult:
    """
    Validate all import edges against architecture layer rules.

    Returns GuardResult with all violations. If .kodara/architecture.yml
    doesn't exist, returns empty GuardResult (no config = no check).
    """
    config_path = kodara_dir / CONFIG_FILE
    layers = load_config(kodara_dir)

    if layers is None:
        return GuardResult(config_path=str(config_path))

    result = GuardResult(config_path=str(config_path))

    for edge in index.graph_edges:
        from_file = edge.get("from", "")
        to_file = edge.get("to", "")

        if not from_file or not to_file:
            continue

        result.checked_edges += 1

        from_layer = _match_layer(from_file, layers)
        to_layer = _match_layer(to_file, layers)

        # Can't determine layer → skip (don't false-positive on unlayered files)
        if from_layer is None or to_layer is None:
            continue

        # Same-layer imports are always allowed
        if from_layer == to_layer:
            continue

        allowed = layers.get(from_layer, {}).get("can_import", [])

        if to_layer not in allowed:
            result.violations.append(Violation(
                from_file=from_file,
                to_file=to_file,
                from_layer=from_layer,
                to_layer=to_layer,
                rule=f"{from_layer} cannot import {to_layer}",
            ))

    return result


# ── Auto-detect ────────────────────────────────────────────────────────────────


def generate_config(root: Path, kodara_dir: Path) -> tuple[dict, Path]:
    """
    Scan project directory structure and generate architecture.yml config.
    Detects standard layer directories (api/, service/, repository/, utils/).

    Returns (config_dict, config_path).
    """
    detected_layers: dict[str, dict] = {}

    # Collect first-level directory names
    try:
        dir_names = {
            d.name.lower()
            for d in root.iterdir()
            if d.is_dir() and not d.name.startswith(".")
        }
    except PermissionError:
        dir_names = set()

    for layer_name, patterns in AUTO_DETECT_LAYERS:
        matched_patterns = []
        for pattern in patterns:
            # Extract directory name from pattern like "api/*" → "api"
            dir_part = pattern.split("/")[0]
            if dir_part in dir_names:
                matched_patterns.append(pattern)

        if matched_patterns:
            detected_layers[layer_name] = {
                "patterns": matched_patterns,
                "can_import": list(DEFAULT_RULES.get(layer_name, [])),
            }

    # Prune can_import to only reference detected layers
    detected_names = set(detected_layers.keys())
    for cfg in detected_layers.values():
        cfg["can_import"] = [l for l in cfg["can_import"] if l in detected_names]

    config = {"architecture": detected_layers}
    config_path = kodara_dir / CONFIG_FILE

    return config, config_path
