"""
Phase 24: Emergent Specialization — Agents Self-Evolve Roles.

Agents don't just follow instructions — they observe which tasks they're
best at, self-specialize, and evolve their own skill profiles based on
performance data. The swarm develops division of labor organically.

How it works:
  1. OBSERVE — Track every task an agent completes: category, rating, speed.
  2. ANALYZE — Compute performance signatures: where does this agent excel?
  3. RECOMMEND — Suggest skill profile updates based on observed strengths.
  4. EVOLVE — Auto-adjust agent skill weights so the dispatcher routes
     better-fitting tasks to them over time.
  5. SPECIALIZE — Agents naturally cluster into roles (architect, tester,
     debugger) without anyone assigning them.

Storage:
  state/broker/specialization/
    agents/{session_id}.json  — performance history and evolved profile
    clusters.json             — discovered specialization clusters
"""

import json
import math
import uuid
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from brynq.server import (
    BROKER_DIR,
    SESSION_ID,
    SESSION_NAME,
    PLATFORM,
    _write_json_sync,
    _append_jsonl_sync,
    CHANNELS_DIR,
)

# ── Paths ──────────────────────────────────────────────────────────────────

SPEC_DIR = BROKER_DIR / "specialization"
AGENTS_DIR = SPEC_DIR / "agents"
CLUSTERS_FILE = SPEC_DIR / "clusters.json"

for _d in (SPEC_DIR, AGENTS_DIR):
    _d.mkdir(parents=True, exist_ok=True)


# ── Performance Recording ─────────────────────────────────────────────────


def record_task_performance(
    session_id: str,
    task_id: str,
    category: str,
    skills_used: list[str],
    rating: float,
    duration_seconds: float,
    success: bool = True,
) -> dict:
    """Record an agent's performance on a completed task.

    This is the raw data that drives specialization. Call this after
    every task completion to build the agent's performance history.
    """
    agent = _load_agent(session_id)

    entry = {
        "task_id": task_id,
        "category": category,
        "skills_used": skills_used,
        "rating": max(1.0, min(5.0, float(rating))),
        "duration_seconds": duration_seconds,
        "success": success,
        "recorded_at": datetime.now(timezone.utc).isoformat(),
    }

    agent.setdefault("history", []).append(entry)
    # Keep last 200 entries
    agent["history"] = agent["history"][-200:]
    agent["total_tasks"] = agent.get("total_tasks", 0) + 1
    agent["updated"] = datetime.now(timezone.utc).isoformat()

    _save_agent(session_id, agent)
    return entry


def get_performance_history(
    session_id: str,
    category: Optional[str] = None,
    limit: int = 50,
) -> list[dict]:
    """Get an agent's task performance history."""
    agent = _load_agent(session_id)
    history = agent.get("history", [])
    if category:
        history = [h for h in history if h.get("category") == category]
    return history[-limit:]


# ── Performance Analysis ───────────────────────────────────────────────────


def analyze_strengths(session_id: str) -> dict:
    """Analyze an agent's performance history to identify strengths.

    Returns a profile of the agent's capabilities based on observed data,
    not just self-declared skills.
    """
    agent = _load_agent(session_id)
    history = agent.get("history", [])

    if not history:
        return {
            "session_id": session_id,
            "total_tasks": 0,
            "strengths": [],
            "weaknesses": [],
            "recommended_skills": [],
            "specialization_score": 0.0,
        }

    # Aggregate by category
    cat_stats: dict[str, dict] = {}
    skill_stats: dict[str, dict] = {}

    for entry in history:
        cat = entry.get("category", "unknown")
        if cat not in cat_stats:
            cat_stats[cat] = {"count": 0, "total_rating": 0.0, "total_duration": 0.0, "successes": 0}
        cat_stats[cat]["count"] += 1
        cat_stats[cat]["total_rating"] += entry.get("rating", 3.0)
        cat_stats[cat]["total_duration"] += entry.get("duration_seconds", 0)
        if entry.get("success", True):
            cat_stats[cat]["successes"] += 1

        for skill in entry.get("skills_used", []):
            if skill not in skill_stats:
                skill_stats[skill] = {"count": 0, "total_rating": 0.0, "successes": 0}
            skill_stats[skill]["count"] += 1
            skill_stats[skill]["total_rating"] += entry.get("rating", 3.0)
            if entry.get("success", True):
                skill_stats[skill]["successes"] += 1

    # Compute strength scores per category
    strengths = []
    weaknesses = []
    for cat, stats in cat_stats.items():
        avg_rating = stats["total_rating"] / stats["count"]
        success_rate = stats["successes"] / stats["count"]
        avg_duration = stats["total_duration"] / stats["count"]

        score = round(avg_rating * success_rate * min(math.log2(stats["count"] + 1), 5), 2)

        entry = {
            "category": cat,
            "tasks_completed": stats["count"],
            "avg_rating": round(avg_rating, 2),
            "success_rate": round(success_rate, 3),
            "avg_duration_sec": round(avg_duration, 1),
            "strength_score": score,
        }

        if avg_rating >= 4.0 and success_rate >= 0.8:
            strengths.append(entry)
        elif avg_rating < 3.0 or success_rate < 0.5:
            weaknesses.append(entry)

    strengths.sort(key=lambda s: s["strength_score"], reverse=True)
    weaknesses.sort(key=lambda w: w["strength_score"])

    # Recommended skills based on high-performing skill usage
    recommended = []
    for skill, stats in skill_stats.items():
        avg_r = stats["total_rating"] / stats["count"]
        sr = stats["successes"] / stats["count"]
        if avg_r >= 4.0 and stats["count"] >= 2:
            recommended.append({
                "skill": skill,
                "evidence_count": stats["count"],
                "avg_rating": round(avg_r, 2),
                "success_rate": round(sr, 3),
            })
    recommended.sort(key=lambda r: r["avg_rating"], reverse=True)

    # Specialization score: how focused is this agent?
    # High specialization = most tasks in few categories
    # Low specialization = tasks spread evenly across many categories
    counts = [s["count"] for s in cat_stats.values()]
    total = sum(counts)
    if total > 0 and len(counts) > 1:
        # Normalized entropy (0 = perfectly specialized, 1 = perfectly generalized)
        probs = [c / total for c in counts]
        entropy = -sum(p * math.log2(p) for p in probs if p > 0)
        max_entropy = math.log2(len(counts))
        specialization = round(1.0 - (entropy / max_entropy), 3) if max_entropy > 0 else 1.0
    else:
        specialization = 1.0 if total > 0 else 0.0

    return {
        "session_id": session_id,
        "total_tasks": len(history),
        "strengths": strengths,
        "weaknesses": weaknesses,
        "recommended_skills": recommended,
        "specialization_score": specialization,
        "category_count": len(cat_stats),
    }


# ── Skill Evolution ───────────────────────────────────────────────────────


def evolve_skills(session_id: str, apply: bool = False) -> dict:
    """Generate evolved skill recommendations based on performance data.

    If apply=True, updates the agent's profile in profiles.py with the
    recommended skills. Otherwise returns the recommendation only.
    """
    analysis = analyze_strengths(session_id)
    recommendations = analysis.get("recommended_skills", [])

    evolved = {
        "session_id": session_id,
        "recommended_additions": [r["skill"] for r in recommendations[:10]],
        "specialization_score": analysis["specialization_score"],
        "based_on_tasks": analysis["total_tasks"],
        "applied": False,
    }

    if apply and recommendations:
        try:
            from brynq.profiles import add_skill
            for rec in recommendations[:10]:
                add_skill(rec["skill"], session_id=session_id)
            evolved["applied"] = True
        except ImportError:
            evolved["error"] = "profiles module not available"

    return evolved


def get_agent_archetype(session_id: str) -> dict:
    """Determine an agent's natural archetype based on performance patterns.

    Archetypes emerge from observed behavior, not assignment:
      - Architect: excels at design, planning, architecture tasks
      - Builder: high output on implementation/coding tasks
      - Guardian: strong at review, testing, security tasks
      - Fixer: specializes in bug fixes and debugging
      - Generalist: balanced across all categories
    """
    analysis = analyze_strengths(session_id)
    strengths = analysis.get("strengths", [])

    if not strengths:
        return {
            "session_id": session_id,
            "archetype": "newcomer",
            "confidence": 0.0,
            "reason": "Not enough task history to determine archetype",
        }

    # Map categories to archetype signals
    archetype_signals: dict[str, float] = {
        "architect": 0.0,
        "builder": 0.0,
        "guardian": 0.0,
        "fixer": 0.0,
    }

    category_map = {
        "architecture": "architect", "design": "architect", "planning": "architect",
        "coding": "builder", "implementation": "builder", "feature": "builder",
        "feature_gap": "builder", "refactoring": "builder",
        "review": "guardian", "testing": "guardian", "security": "guardian",
        "security_issue": "guardian", "missing_tests": "guardian",
        "bug_fix": "fixer", "debugging": "fixer", "incident": "fixer",
    }

    for s in strengths:
        cat = s["category"].lower()
        archetype = category_map.get(cat)
        if archetype:
            archetype_signals[archetype] += s["strength_score"]
        else:
            # Spread unrecognized categories across all
            for a in archetype_signals:
                archetype_signals[a] += s["strength_score"] * 0.25

    total_signal = sum(archetype_signals.values())
    if total_signal == 0:
        return {
            "session_id": session_id,
            "archetype": "generalist",
            "confidence": 0.5,
            "reason": "Balanced performance across categories",
        }

    # Find dominant archetype
    best = max(archetype_signals, key=lambda k: archetype_signals[k])
    confidence = round(archetype_signals[best] / total_signal, 3)

    # Generalist if no clear winner
    if confidence < 0.4:
        return {
            "session_id": session_id,
            "archetype": "generalist",
            "confidence": round(1.0 - confidence, 3),
            "reason": "Balanced performance — no dominant specialization",
            "signals": {k: round(v, 2) for k, v in archetype_signals.items()},
        }

    return {
        "session_id": session_id,
        "archetype": best,
        "confidence": confidence,
        "reason": f"Strong performance in {best}-aligned categories",
        "signals": {k: round(v, 2) for k, v in archetype_signals.items()},
    }


# ── Swarm Specialization Clusters ─────────────────────────────────────────


def compute_clusters() -> dict:
    """Analyze all agents and discover natural specialization clusters.

    This reveals the swarm's emergent division of labor.
    """
    agent_files = list(AGENTS_DIR.glob("*.json"))
    if not agent_files:
        return {"clusters": {}, "total_agents": 0, "unclustered": 0}

    clusters: dict[str, list[str]] = {}
    unclustered = []

    for f in agent_files:
        sid = f.stem
        archetype = get_agent_archetype(sid)
        arch_name = archetype.get("archetype", "unknown")

        if arch_name in ("newcomer", "unknown"):
            unclustered.append(sid)
        else:
            clusters.setdefault(arch_name, []).append(sid)

    result = {
        "clusters": {
            name: {
                "agents": agents,
                "count": len(agents),
            }
            for name, agents in clusters.items()
        },
        "total_agents": len(agent_files),
        "unclustered": len(unclustered),
        "unclustered_agents": unclustered,
        "computed_at": datetime.now(timezone.utc).isoformat(),
    }

    _write_json_sync(CLUSTERS_FILE, result)
    return result


def get_clusters() -> dict:
    """Get the most recently computed specialization clusters."""
    try:
        return json.loads(CLUSTERS_FILE.read_text("utf-8"))
    except (FileNotFoundError, json.JSONDecodeError, OSError):
        return {"clusters": {}, "total_agents": 0, "unclustered": 0}


def find_specialist(category: str) -> list[dict]:
    """Find the best specialist agents for a given task category.

    Returns agents ranked by their strength score in that category.
    """
    results = []
    for f in AGENTS_DIR.glob("*.json"):
        sid = f.stem
        analysis = analyze_strengths(sid)
        for s in analysis.get("strengths", []):
            if s["category"].lower() == category.lower():
                results.append({
                    "session_id": sid,
                    "category": category,
                    "strength_score": s["strength_score"],
                    "avg_rating": s["avg_rating"],
                    "tasks_completed": s["tasks_completed"],
                    "archetype": get_agent_archetype(sid).get("archetype", "unknown"),
                })
    results.sort(key=lambda r: r["strength_score"], reverse=True)
    return results


# ── Internals ──────────────────────────────────────────────────────────────


def _load_agent(session_id: str) -> dict:
    path = AGENTS_DIR / f"{session_id}.json"
    try:
        return json.loads(path.read_text("utf-8"))
    except (FileNotFoundError, json.JSONDecodeError, OSError):
        return {"session_id": session_id, "history": [], "total_tasks": 0}


def _save_agent(session_id: str, data: dict) -> None:
    _write_json_sync(AGENTS_DIR / f"{session_id}.json", data)
