"""Versioned definitions and dependency impact. No current server facts."""
import hashlib
import json
from pathlib import Path
from .paths import root


def read_json(path):
    return json.loads(Path(path).read_text(encoding="utf-8"))


class Catalog:
    def __init__(self, base=None):
        self.root = Path(base) if base else root()
        self.sources = read_json(self.root / "data/sources.json")["sources"]
        self.verify_sources()
        self.quests = self.load("quest-catalog.json")["quests"]
        self.movement = self.load("movement-catalog.json")
        self.gather = self.load("gather_catalog.json")
        self.processing = self.load("processing_catalog.json")
        self.routes = self.load("action-paths.json")
        self.contracts = read_json(self.root / "data/contracts.json")
        self.coverage = read_json(self.root / "data/coverage.json")
        self.dependencies = read_json(self.root / "data/dependencies.json")
        self.fingerprint = hashlib.sha256(json.dumps({"sources": self.sources, "contracts": self.contracts,
             "dependencies": self.dependencies}, sort_keys=True).encode()).hexdigest()

    def verify_sources(self):
        for source in self.sources:
            path = (self.root / source["snapshot"]).resolve()
            if not path.is_relative_to(self.root.resolve()):
                raise ValueError("Source snapshot escapes installation")
            if hashlib.sha256(path.read_bytes()).hexdigest() != source["sha256"]:
                raise ValueError("Source changed; review impact before execution: " + source["id"])

    def load(self, name):
        return read_json(self.root / "data/snapshots" / name)

    def quest(self, identifier):
        return next((q for q in self.quests if str(q["id"]) == str(identifier)), None)

    def search(self, query="", limit=100):
        rows = []
        for quest in self.quests:
            rows.append({"id": str(quest["id"]), "name": quest["title"], "kind": "quest", "source": "quest-catalog",
                         "status": "정적 정의 · 수락/현재 진행 미확인"})
        for group in self.gather["groups"]:
            for node in group["nodes"]:
                rows.append({"id": node["id"], "name": node["name"], "kind": "gather", "source": "gather-catalog", "status": "과거 목록 · 현재 해금 미확인"})
        for recipe in self.processing["recipes"]:
            rows.append({"id": recipe["id"], "name": recipe.get("name", recipe["id"]), "kind": "processing", "source": "processing-catalog", "status": "과거 목록 · 실행 미연결"})
        return [r for r in rows if query.casefold() in str(r).casefold()][:limit]

    def impact(self, changed):
        known = set(self.dependencies)
        known.update(v for values in self.dependencies.values() for v in values)
        if any(k not in known for k in changed):
            raise ValueError("Unknown dependency identifier")
        affected, frontier = set(changed), list(changed)
        while frontier:
            for target in self.dependencies.get(frontier.pop(), []):
                if target not in affected:
                    affected.add(target)
                    frontier.append(target)
        return sorted(affected)

    def inspect_update(self, source_id, candidate):
        source = next(s for s in self.sources if s["id"] == source_id)
        new_hash = hashlib.sha256(Path(candidate).read_bytes()).hexdigest()
        changed = new_hash != source["sha256"]
        return {"source": source_id, "changed": changed, "old_sha256": source["sha256"], "candidate_sha256": new_hash,
                "invalidate": self.impact([source_id]) if changed else [], "applied": False}
