"""
Registry — projenin hafızası.
AI her fonksiyon yazdığında buraya kaydeder.
Her session başında buradan okur.
"""

import json
from dataclasses import dataclass, field, asdict
from typing import Optional
from pathlib import Path
from datetime import datetime

try:
    import filelock

    HAS_FILELOCK = True
except ImportError:
    HAS_FILELOCK = False

REGISTRY_FILE = "aicontract_registry.json"
CONTRACT_FILE = "aicontract.yaml"


@dataclass
class FunctionContract:
    name: str
    file: str
    domain: str
    touches: list = field(default_factory=list)
    called_by: list = field(default_factory=list)
    replaces: Optional[str] = None
    line: int = 0
    is_async: bool = False
    risk: str = "low"
    registered_at: str = field(
        default_factory=lambda: datetime.now().isoformat()
    )  # low / medium / high


@dataclass
class DomainContract:
    name: str
    owner: Optional[str] = None
    owns: list = field(default_factory=list)
    touches: list = field(default_factory=list)


class Registry:
    def __init__(self, project_root: str = "."):
        self.project_root = Path(project_root)
        self.registry_path = self.project_root / REGISTRY_FILE
        self.functions: dict[str, FunctionContract] = {}
        self.domains: dict[str, DomainContract] = {}
        self.rules: dict = {}
        self._load()

    def _load(self):
        """Mevcut registry'yi yükle."""
        lock_path = str(self.registry_path) + ".lock"
        load_func = self._load_with_lock if HAS_FILELOCK else self._load_plain
        load_func(lock_path)

        contract_path = self.project_root / CONTRACT_FILE
        if contract_path.exists():
            self._load_yaml(contract_path)

    def _load_plain(self, lock_path: str):
        if self.registry_path.exists():
            with open(self.registry_path) as f:
                data = json.load(f)
            for name, fc in data.get("functions", {}).items():
                self.functions[name] = FunctionContract(**fc)
            for name, dc in data.get("domains", {}).items():
                self.domains[name] = DomainContract(**dc)
            self.rules = data.get("rules", {})

    def _load_with_lock(self, lock_path: str):
        if not self.registry_path.exists():
            return
        with filelock.FileLock(lock_path):
            with open(self.registry_path) as f:
                data = json.load(f)
            for name, fc in data.get("functions", {}).items():
                self.functions[name] = FunctionContract(**fc)
            for name, dc in data.get("domains", {}).items():
                self.domains[name] = DomainContract(**dc)
            self.rules = data.get("rules", {})

    def _load_yaml(self, path: Path):
        try:
            import yaml
        except ImportError:
            return

        with open(path) as f:
            data = yaml.safe_load(f)

        if not data:
            return

        for name, dc in (data.get("domains") or {}).items():
            if name not in self.domains:
                self.domains[name] = DomainContract(
                    name=name,
                    owner=dc.get("owner"),
                    owns=dc.get("owns", []),
                    touches=dc.get("touches", []),
                )

        self.rules = data.get("rules", {})

    def save(self):
        """Registry'yi diske yaz."""
        data = {
            "functions": {k: asdict(v) for k, v in self.functions.items()},
            "domains": {k: asdict(v) for k, v in self.domains.items()},
            "rules": self.rules,
        }
        if HAS_FILELOCK:
            lock_path = str(self.registry_path) + ".lock"
            with filelock.FileLock(lock_path):
                with open(self.registry_path, "w") as f:
                    json.dump(data, f, indent=2, ensure_ascii=False)
        else:
            with open(self.registry_path, "w") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)

    def register(self, contract: FunctionContract) -> FunctionContract:
        """Fonksiyonu kaydet, geri dön."""
        self.functions[contract.name] = contract
        # domain owns listesini güncelle
        if contract.domain in self.domains:
            d = self.domains[contract.domain]
            if contract.name not in d.owns:
                d.owns.append(contract.name)
        else:
            self.domains[contract.domain] = DomainContract(
                name=contract.domain,
                owner=contract.file,
                owns=[contract.name],
                touches=contract.touches,
            )
        self.save()
        return contract

    def get(self, func_name: str) -> Optional[FunctionContract]:
        return self.functions.get(func_name)

    def all_functions(self) -> list[FunctionContract]:
        return list(self.functions.values())

    def all_domains(self) -> list[DomainContract]:
        return list(self.domains.values())

    def empty_domains(self) -> list[DomainContract]:
        """Sahibi olmayan domainler — AI'ın doldurabileceği alanlar."""
        return [d for d in self.domains.values() if not d.owner or not d.owns]

    def functions_in_domain(self, domain: str) -> list[FunctionContract]:
        return [f for f in self.functions.values() if f.domain == domain]

    def high_risk_functions(self) -> list[FunctionContract]:
        return [f for f in self.functions.values() if f.risk == "high"]

    def cleanup(self, project_root: str = ".") -> dict:
        """
        EKSİK 2 — Registry temizleme.
        Dosyası silinmiş veya fonksiyonu kalkmış kayıtları temizle.
        Döner: {"removed": [...], "kept": int}
        """
        import ast as _ast

        removed = []
        to_delete = []

        for name, contract in self.functions.items():
            path = Path(contract.file)

            if not path.exists():
                to_delete.append(name)
                removed.append(
                    {"name": name, "reason": "dosya silindi", "file": contract.file}
                )
                continue

            try:
                source = path.read_text(encoding="utf-8")
                tree = _ast.parse(source)
                func_names = {
                    node.name
                    for node in _ast.walk(tree)
                    if isinstance(node, (_ast.FunctionDef, _ast.AsyncFunctionDef))
                }
                if name not in func_names:
                    to_delete.append(name)
                    removed.append(
                        {
                            "name": name,
                            "reason": "fonksiyon kaldırıldı",
                            "file": contract.file,
                        }
                    )
            except Exception:
                pass

        for name in to_delete:
            del self.functions[name]
            for domain in self.domains.values():
                if name in domain.owns:
                    domain.owns.remove(name)

        if to_delete:
            self.save()

        return {"removed": removed, "kept": len(self.functions)}
