#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""D15 Phylogenetic Skill Compiler — Data collectors and ranking."""
from __future__ import annotations

from datetime import datetime, timezone
from pathlib import Path

from pulse.core.brain_utils import (
    HIPPOCAMPUS_DIR, STATE_DIR, load_json_dict,
    LESSONS_JSONL, FAILS_JSONL, PATTERNS_JSONL, read_jsonl_entries,
)

# ── Source paths ───────────────────────────────────────────────────────
GOLDEN_PATHS_FILE = STATE_DIR / "golden_paths.json"
HEBBIAN_FILE = STATE_DIR / "hebbian_matrix.json"
SCHEMAS_FILE = HIPPOCAMPUS_DIR / "Patterns" / "schemas.json"
PROCEDURAL_LOG_FILE = HIPPOCAMPUS_DIR / "Evidence" / "Metrics" / "procedural_log.json"
REFLEX_FILE = HIPPOCAMPUS_DIR / "Anti_Patterns" / "reflex_patterns.json"

CANONICAL_DOMAINS = {
    "brain", "general", "infrastructure", "docs", "ui", "performance",
    "architecture", "security", "testing", "neural_infra", "language",
    "detection", "handler", "deployment", "system", "swarm", "agent",
    "cli", "api", "config", "graph", "consolidation", "bridge",
    "synthesis", "prediction", "governance", "memory", "extraction",
    "skill", "daemon", "worker",
}
_DOMAIN_ALIASES: dict[str, str] = {
    "neural": "neural_infra", "infra": "infrastructure",
    "test": "testing", "tests": "testing",
    "doc": "docs", "documentation": "docs",
    "perf": "performance", "arch": "architecture",
    "sec": "security", "deploy": "deployment",
    "agents": "agent", "workers": "worker", "daemons": "daemon",
    "skills": "skill", "memories": "memory", "extract": "extraction",
    "search": "brain", "knowledge": "brain", "boot": "brain",
}


def _normalize_domain(raw: str) -> str | None:
    """Map noisy domain names to canonical set. Returns None for garbage."""
    if not raw or not isinstance(raw, str):
        return None
    d = raw.strip().lower().replace("-", "_")
    if len(d) > 50:
        return None
    if any(c in d for c in ("(", ")", ",", "http", "python", "`", "{")):
        return None
    if d in CANONICAL_DOMAINS:
        return d
    if d in _DOMAIN_ALIASES:
        return _DOMAIN_ALIASES[d]
    for canon in CANONICAL_DOMAINS:
        if canon in d or d in canon:
            return canon
    return None


def _collect_golden_paths() -> dict[str, list[dict]]:
    result: dict[str, list[dict]] = {}
    for path in load_json_dict(GOLDEN_PATHS_FILE).get("paths", []):
        if path.get("status") != "active":
            continue
        domain = _normalize_domain(path.get("domain", ""))
        if not domain:
            continue
        steps = path.get("steps", [])
        steps = [s.replace("Completed: ", "", 1) if s.startswith("Completed: ") else s for s in steps]
        title = path.get("title", "")
        text = f"{title}: {' > '.join(steps[:5])}" if steps else title
        result.setdefault(domain, []).append({
            "text": text[:120], "type": "golden_path",
            "confidence": path.get("confidence", 0.5),
            "frequency": path.get("observation_count", 1),
            "timestamp": path.get("timestamp", ""),
        })
    return result


def _collect_hebbian() -> dict[str, list[dict]]:
    result: dict[str, list[dict]] = {}
    matrix = load_json_dict(HEBBIAN_FILE).get("matrix", {})
    agg: dict[str, dict] = {}
    for _agent, domains in matrix.items():
        if not isinstance(domains, dict):
            continue
        for raw_domain, stats in domains.items():
            if not isinstance(stats, dict):
                continue
            domain = _normalize_domain(raw_domain)
            if not domain:
                continue
            a = agg.setdefault(domain, {"s": 0, "f": 0, "n": 0})
            a["s"] += stats.get("successes", 0)
            a["f"] += stats.get("failures", 0)
            a["n"] += 1
    for domain, a in agg.items():
        total = a["s"] + a["f"]
        if total == 0:
            continue
        rate = a["s"] / total
        result.setdefault(domain, []).append({
            "text": f"Domain '{domain}': {a['s']}S/{a['f']}F across {a['n']} agents ({rate:.0%})",
            "type": "hebbian_stat", "confidence": rate,
            "frequency": total, "timestamp": "",
        })
    return result


def _collect_schemas() -> dict[str, list[dict]]:
    result: dict[str, list[dict]] = {}
    for s in load_json_dict(SCHEMAS_FILE).get("schemas", []):
        domain = _normalize_domain(s.get("domain", ""))
        if not domain:
            continue
        text = f"{s.get('name', '')}: {s.get('description', '')}"
        result.setdefault(domain, []).append({
            "text": text[:120], "type": "schema",
            "confidence": s.get("confidence", 0.5),
            "frequency": 1, "timestamp": s.get("created", ""),
        })
    return result


def _collect_procedures() -> dict[str, list[dict]]:
    result: dict[str, list[dict]] = {}
    for p in load_json_dict(PROCEDURAL_LOG_FILE).get("procedures", []):
        domain = _normalize_domain(p.get("domain", ""))
        if not domain:
            continue
        name = p.get("name", p.get("procedure_id", ""))
        execs = p.get("executions", 0)
        text = f"Procedure '{name}': {p.get('success_rate', 0):.0%} over {execs} runs"
        result.setdefault(domain, []).append({
            "text": text[:120], "type": "procedure",
            "confidence": p.get("confidence", 0.5),
            "frequency": execs, "timestamp": p.get("last_executed", ""),
        })
    return result


def _collect_reflex() -> dict[str, list[dict]]:
    result: dict[str, list[dict]] = {}
    for e in load_json_dict(REFLEX_FILE).get("entries", []):
        if e.get("status") != "active":
            continue
        domain = _normalize_domain(e.get("domain", ""))
        if not domain:
            continue
        text = e.get("description", "")[:120]
        if len(text) < 15:
            continue
        result.setdefault(domain, []).append({
            "text": text, "type": f"reflex_{e.get('pattern_type', 'reflex')}",
            "confidence": e.get("confidence", 0.5),
            "frequency": 1, "timestamp": e.get("timestamp", ""),
        })
    return result


def _collect_jsonl(jsonl_path: Path, item_type: str) -> dict[str, list[dict]]:
    """Collect items from a JSONL brain file, grouped by domain."""
    result: dict[str, list[dict]] = {}
    for entry in read_jsonl_entries(jsonl_path):
        title = (entry.get("title") or entry.get("content") or entry.get("description", ""))[:120]
        if len(title) < 15:
            continue
        domain = _normalize_domain(entry.get("domain", "")) or "general"
        confidence = entry.get("confidence", 0.5)
        if isinstance(confidence, (int, float)):
            confidence = min(float(confidence), 1.0)
        else:
            confidence = 0.5
        consol = entry.get("consolidation_count", 1)
        timestamp = entry.get("timestamp") or entry.get("date", "")
        result.setdefault(domain, []).append({
            "text": title, "type": item_type,
            "confidence": confidence,
            "frequency": max(int(consol), 1) if isinstance(consol, (int, float)) else 1,
            "timestamp": timestamp[:10] if timestamp else "",
        })
    return result


# Registry: (name, collector_fn) — used by pipeline
COLLECTORS = [
    ("golden_paths", _collect_golden_paths),
    ("hebbian_matrix", _collect_hebbian),
    ("schemas", _collect_schemas),
    ("procedures", _collect_procedures),
    ("reflex_patterns", _collect_reflex),
    ("lessons", lambda: _collect_jsonl(LESSONS_JSONL, "lesson")),
    ("failures", lambda: _collect_jsonl(FAILS_JSONL, "failure")),
    ("patterns", lambda: _collect_jsonl(PATTERNS_JSONL, "pattern")),
]


def _aggregate(collections: list[dict[str, list[dict]]]) -> dict[str, dict]:
    """Merge all source collections into unified per-domain profiles."""
    domains: dict[str, dict] = {}
    for coll in collections:
        for domain, items in coll.items():
            if domain not in domains:
                domains[domain] = {"items": [], "sources": set()}
            domains[domain]["items"].extend(items)
            for item in items:
                domains[domain]["sources"].add(item["type"])
    for d in domains.values():
        d["sources"] = sorted(d["sources"])
    return domains


def _recency_score(timestamp: str) -> float:
    if not timestamp:
        return 0.3
    try:
        if len(timestamp) == 10:
            ts = datetime.strptime(timestamp, "%Y-%m-%d").replace(tzinfo=timezone.utc)
        else:
            ts = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
            if ts.tzinfo is None:
                ts = ts.replace(tzinfo=timezone.utc)
        age = (datetime.now(timezone.utc) - ts).total_seconds() / 86400
        if age <= 1:
            return 1.0
        if age <= 7:
            return 0.8
        if age <= 30:
            return 0.5
        return 0.2
    except (ValueError, TypeError):
        return 0.3


def _rank_items(profile: dict) -> list[dict]:
    """Score: confidence*0.5 + frequency*0.3 + recency*0.2. Cap 15/domain."""
    items = profile["items"]
    max_freq = max((i["frequency"] for i in items), default=1) or 1
    for item in items:
        freq_norm = min(item["frequency"] / max_freq, 1.0)
        item["score"] = round(
            item["confidence"] * 0.5 + freq_norm * 0.3 + _recency_score(item["timestamp"]) * 0.2, 3
        )
    items.sort(key=lambda x: -x["score"])
    return items[:15]


def _build_domain_stats(domain: str, items: list[dict], hebbian_data: dict) -> dict:
    """Compute predecessor count and success rate from hebbian data."""
    matrix = hebbian_data.get("matrix", {})
    predecessors, total_s, total_f = 0, 0, 0
    for _agent, domains in matrix.items():
        if not isinstance(domains, dict):
            continue
        for raw_d, stats in domains.items():
            if _normalize_domain(raw_d) == domain and isinstance(stats, dict):
                predecessors += 1
                total_s += stats.get("successes", 0)
                total_f += stats.get("failures", 0)
    total = total_s + total_f
    return {
        "total_items": len(items),
        "predecessors": predecessors,
        "avg_success_rate": round(total_s / total, 2) if total else 0.0,
    }
