from __future__ import annotations

from collections import Counter
import hashlib
from pathlib import Path
import re
from typing import Any

from .portability import write_portable_json
from .state import REPO_ENGINE_DIR, read_json, write_json

AREA_MEMORY_DIR = Path(REPO_ENGINE_DIR) / "area_memory"
AREAS_PATH = AREA_MEMORY_DIR / "areas.json"
AREA_SHARDS_DIR = AREA_MEMORY_DIR / "areas"


def derive_area_id(paths: list[str]) -> str:
    cleaned = [str(path).strip().lstrip("./") for path in paths if str(path).strip()]
    if not cleaned:
        return "unknown"
    first = cleaned[0].split("/")
    if len(first) >= 2 and first[0] in {"src", "tests", "docs"}:
        return "/".join(first[:2])
    return first[0] if first else "unknown"


def load_area_memory(repo_root: Path) -> dict[str, Any]:
    memory = read_json(repo_root / AREAS_PATH, {"version": 1, "areas": {}})
    if not isinstance(memory, dict):
        memory = {"version": 1, "areas": {}}
    areas = memory.setdefault("areas", {})
    if not isinstance(areas, dict):
        areas = {}
        memory["areas"] = areas
    shards_dir = repo_root / AREA_SHARDS_DIR
    if shards_dir.exists():
        for path in sorted(shards_dir.glob("*.json")):
            payload = read_json(path, {})
            if not isinstance(payload, dict):
                continue
            area_id = str(payload.get("area_id") or path.stem).strip()
            if area_id:
                areas[area_id] = payload
    return memory


def _area_shard_name(area_id: str) -> str:
    raw = str(area_id or "unknown").strip().strip("/")
    cleaned = re.sub(r"[^A-Za-z0-9_.-]+", "-", raw)
    slug = (cleaned or "unknown").strip("-._") or "unknown"
    digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()[:8]
    return f"{slug[:84]}--{digest}"


def _top(counter: dict[str, int], limit: int = 8) -> list[str]:
    return [item for item, _count in Counter(counter).most_common(limit)]


def update_area_memory(repo_root: Path, execution_log: dict[str, Any], *, strategy_stored: bool = False, failure_recorded: bool = False) -> dict[str, Any]:
    files = list(execution_log.get("files_opened", [])) if isinstance(execution_log.get("files_opened"), list) else []
    tests = list(execution_log.get("tests_executed", [])) if isinstance(execution_log.get("tests_executed"), list) else []
    area_id = str(execution_log.get("area_id") or derive_area_id(files + tests))
    memory = load_area_memory(repo_root)
    areas = memory.setdefault("areas", {})
    area = areas.setdefault(
        area_id,
        {
            "area_id": area_id,
            "files": {},
            "tests": {},
            "strategy_count": 0,
            "failure_count": 0,
            "executions": 0,
        },
    )
    area["executions"] = int(area.get("executions", 0) or 0) + 1
    if strategy_stored:
        area["strategy_count"] = int(area.get("strategy_count", 0) or 0) + 1
    if failure_recorded:
        area["failure_count"] = int(area.get("failure_count", 0) or 0) + 1
    for path in files:
        area.setdefault("files", {})[path] = int(area.setdefault("files", {}).get(path, 0) or 0) + 1
    for test in tests:
        area.setdefault("tests", {})[test] = int(area.setdefault("tests", {}).get(test, 0) or 0) + 1
    area["related_files"] = _top(area.get("files", {}))
    area["related_tests"] = _top(area.get("tests", {}))
    write_json(repo_root / AREAS_PATH, memory)
    write_portable_json(repo_root, repo_root / AREA_SHARDS_DIR / f"{_area_shard_name(area_id)}.json", area)
    return area


def migrate_area_memory_shards(repo_root: Path) -> dict[str, Any]:
    memory = load_area_memory(repo_root)
    migrated: list[str] = []
    areas = memory.get("areas") if isinstance(memory.get("areas"), dict) else {}
    for area_id, area in areas.items():
        if not isinstance(area, dict):
            continue
        payload = dict(area)
        payload.setdefault("area_id", str(area_id))
        path = repo_root / AREA_SHARDS_DIR / f"{_area_shard_name(str(area_id))}.json"
        write_portable_json(repo_root, path, payload)
        migrated.append(path.relative_to(repo_root).as_posix())
    return {"migrated": migrated}


def area_hints(repo_root: Path, area_id: str) -> dict[str, Any]:
    area = load_area_memory(repo_root).get("areas", {}).get(area_id, {})
    return {
        "area_id": area_id,
        "related_files": list(area.get("related_files", [])) if isinstance(area.get("related_files"), list) else [],
        "related_tests": list(area.get("related_tests", [])) if isinstance(area.get("related_tests"), list) else [],
        "area_strategy_count": int(area.get("strategy_count", 0) or 0),
        "area_failure_count": int(area.get("failure_count", 0) or 0),
    }
