"""
Phase 43: Per-Agent Performance Analytics.

Deep individual analytics for each agent on the Brynq platform.
This is what a user sees when they click on an agent's profile:
activity logs, performance summaries, skill growth curves,
activity heatmaps, agent comparisons, leaderboards, and weekly reports.

Storage:
  state/broker/agent_analytics/{agent_id}.jsonl  -- append-only activity log
"""

import json
from collections import Counter, defaultdict
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Optional

from brynq.server import (
    BROKER_DIR,
    CHANNELS_DIR,
    SESSIONS_DIR,
    _read_jsonl_sync,
    _write_json_sync,
    _append_jsonl_sync,
)

# -- Paths -----------------------------------------------------------------

AGENT_ANALYTICS_DIR = BROKER_DIR / "agent_analytics"
AGENT_ANALYTICS_DIR.mkdir(parents=True, exist_ok=True)

# -- Valid activity types --------------------------------------------------

VALID_ACTIVITY_TYPES = frozenset({
    "message_sent",
    "task_completed",
    "task_failed",
    "task_accepted",
    "skill_used",
    "login",
    "collaboration",
    "review_given",
    "review_received",
})

# -- Helpers ---------------------------------------------------------------


def _activity_log_path(agent_id: str) -> Path:
    """Return the JSONL path for an agent's activity log."""
    return AGENT_ANALYTICS_DIR / f"{agent_id}.jsonl"


def _read_activities(agent_id: str) -> list[dict]:
    """Read all activity entries for an agent."""
    return _read_jsonl_sync(_activity_log_path(agent_id))


def _filter_by_time(entries: list[dict], hours: float) -> list[dict]:
    """Return entries whose timestamp falls within the last *hours* hours."""
    cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
    result = []
    for e in entries:
        ts = e.get("timestamp")
        if ts is None:
            continue
        try:
            dt = datetime.fromisoformat(ts)
        except (ValueError, TypeError):
            continue
        if dt >= cutoff:
            result.append(e)
    return result


# -- 1. Record Activity ----------------------------------------------------


def record_activity(
    agent_id: str,
    activity_type: str,
    details: Optional[dict] = None,
) -> dict:
    """Log an agent action.

    Parameters
    ----------
    agent_id : str
        The agent's session / profile identifier.
    activity_type : str
        One of the VALID_ACTIVITY_TYPES.
    details : dict, optional
        Arbitrary metadata for this activity (task_id, skill name, etc.).

    Returns
    -------
    dict  The recorded entry.
    """
    if activity_type not in VALID_ACTIVITY_TYPES:
        return {"error": f"Invalid activity_type '{activity_type}'. Must be one of {sorted(VALID_ACTIVITY_TYPES)}"}

    now = datetime.now(timezone.utc).isoformat()
    entry = {
        "agent_id": agent_id,
        "activity_type": activity_type,
        "timestamp": now,
        "details": details or {},
    }

    path = _activity_log_path(agent_id)
    path.parent.mkdir(parents=True, exist_ok=True)
    _append_jsonl_sync(path, entry)
    return entry


# -- 2. Performance Summary ------------------------------------------------


def get_performance(agent_id: str, hours: float = 24) -> dict:
    """Return a performance summary for *agent_id* over the last *hours* hours.

    Keys returned:
      total_tasks, completed, failed, success_rate,
      avg_completion_time_minutes, messages_sent,
      skills_used (Counter), collaboration_count,
      reputation_trend (up / down / stable).
    """
    all_entries = _read_activities(agent_id)
    entries = _filter_by_time(all_entries, hours)

    completed = [e for e in entries if e["activity_type"] == "task_completed"]
    failed = [e for e in entries if e["activity_type"] == "task_failed"]
    total_tasks = len(completed) + len(failed)
    success_rate = (len(completed) / total_tasks * 100) if total_tasks else 0.0

    # Average completion time from details.duration_minutes
    durations = []
    for e in completed:
        dur = e.get("details", {}).get("duration_minutes")
        if dur is not None:
            try:
                durations.append(float(dur))
            except (ValueError, TypeError):
                pass
    avg_time = (sum(durations) / len(durations)) if durations else 0.0

    messages_sent = sum(1 for e in entries if e["activity_type"] == "message_sent")

    skill_counter: Counter = Counter()
    for e in entries:
        if e["activity_type"] == "skill_used":
            skill_name = e.get("details", {}).get("skill")
            if skill_name:
                skill_counter[skill_name] += 1

    collaborators: set = set()
    for e in entries:
        if e["activity_type"] == "collaboration":
            partner = e.get("details", {}).get("partner_id")
            if partner:
                collaborators.add(partner)

    # Reputation trend: compare current window vs previous window of same length
    prev_entries = _filter_by_time(all_entries, hours * 2)
    prev_only = [e for e in prev_entries if e not in entries]
    prev_completed = sum(1 for e in prev_only if e["activity_type"] == "task_completed")
    curr_completed = len(completed)
    if curr_completed > prev_completed:
        trend = "up"
    elif curr_completed < prev_completed:
        trend = "down"
    else:
        trend = "stable"

    return {
        "agent_id": agent_id,
        "period_hours": hours,
        "total_tasks": total_tasks,
        "completed": len(completed),
        "failed": len(failed),
        "success_rate": round(success_rate, 1),
        "avg_completion_time_minutes": round(avg_time, 2),
        "messages_sent": messages_sent,
        "skills_used": dict(skill_counter),
        "collaboration_count": len(collaborators),
        "reputation_trend": trend,
    }


# -- 3. Skill Growth -------------------------------------------------------


def get_skill_growth(agent_id: str) -> dict:
    """Track how an agent's skills develop over time.

    For each skill the agent has ever used, returns:
      first_used, last_used, times_used, successes, failures,
      success_rate, trend (improving / stable / declining).
    """
    entries = _read_activities(agent_id)

    skill_data: dict[str, dict] = {}

    # Collect skill_used events
    for e in entries:
        if e["activity_type"] == "skill_used":
            skill = e.get("details", {}).get("skill")
            if not skill:
                continue
            success = e.get("details", {}).get("success", True)
            ts = e.get("timestamp", "")

            if skill not in skill_data:
                skill_data[skill] = {
                    "first_used": ts,
                    "last_used": ts,
                    "times_used": 0,
                    "successes": 0,
                    "failures": 0,
                    "history": [],  # list of bools for trend calc
                }
            sd = skill_data[skill]
            sd["times_used"] += 1
            sd["last_used"] = ts
            if success:
                sd["successes"] += 1
            else:
                sd["failures"] += 1
            sd["history"].append(bool(success))

    # Compute per-skill summaries
    result: dict[str, dict] = {}
    for skill, sd in skill_data.items():
        total = sd["times_used"]
        rate = (sd["successes"] / total * 100) if total else 0.0

        # Trend: compare first half success rate vs second half
        history = sd["history"]
        if len(history) < 4:
            trend = "stable"
        else:
            mid = len(history) // 2
            first_half = history[:mid]
            second_half = history[mid:]
            r1 = sum(first_half) / len(first_half) if first_half else 0
            r2 = sum(second_half) / len(second_half) if second_half else 0
            if r2 - r1 > 0.1:
                trend = "improving"
            elif r1 - r2 > 0.1:
                trend = "declining"
            else:
                trend = "stable"

        result[skill] = {
            "first_used": sd["first_used"],
            "last_used": sd["last_used"],
            "times_used": total,
            "successes": sd["successes"],
            "failures": sd["failures"],
            "success_rate": round(rate, 1),
            "trend": trend,
        }

    return {"agent_id": agent_id, "skills": result}


# -- 4. Activity Heatmap ---------------------------------------------------


def get_activity_heatmap(agent_id: str, days: int = 7) -> dict:
    """Return activity counts per hour-of-day for each of the last *days* days.

    Structure::

        {
          "agent_id": "...",
          "days": 7,
          "heatmap": {
            "2026-03-10": {0: 2, 1: 0, ..., 23: 5},
            ...
          },
          "total_activities": 42
        }
    """
    entries = _filter_by_time(_read_activities(agent_id), days * 24)

    heatmap: dict[str, dict[int, int]] = {}
    for e in entries:
        ts = e.get("timestamp")
        if not ts:
            continue
        try:
            dt = datetime.fromisoformat(ts)
        except (ValueError, TypeError):
            continue
        day_key = dt.strftime("%Y-%m-%d")
        hour = dt.hour
        if day_key not in heatmap:
            heatmap[day_key] = {h: 0 for h in range(24)}
        heatmap[day_key][hour] = heatmap[day_key].get(hour, 0) + 1

    return {
        "agent_id": agent_id,
        "days": days,
        "heatmap": heatmap,
        "total_activities": len(entries),
    }


# -- 5. Comparison ----------------------------------------------------------


def compare_agents(agent_ids: list[str]) -> dict:
    """Side-by-side performance comparison for 2+ agents.

    Returns per-agent metrics and highlights who leads in each category.
    """
    if len(agent_ids) < 2:
        return {"error": "Need at least 2 agent IDs to compare."}

    profiles: dict[str, dict] = {}
    for aid in agent_ids:
        profiles[aid] = get_performance(aid, hours=24)

    # Determine leaders per metric
    metrics_to_compare = [
        ("completed", True),        # higher is better
        ("success_rate", True),
        ("messages_sent", True),
        ("collaboration_count", True),
        ("avg_completion_time_minutes", False),  # lower is better
    ]

    leaders: dict[str, Optional[str]] = {}
    for metric, higher_better in metrics_to_compare:
        values = {aid: profiles[aid].get(metric, 0) for aid in agent_ids}
        # Filter out zeros for avg_completion_time comparison
        non_zero = {aid: v for aid, v in values.items() if v != 0}
        if not non_zero:
            leaders[metric] = None
            continue
        if higher_better:
            leaders[metric] = max(non_zero, key=non_zero.get)  # type: ignore[arg-type]
        else:
            leaders[metric] = min(non_zero, key=non_zero.get)  # type: ignore[arg-type]

    return {
        "agents": profiles,
        "leaders": leaders,
    }


# -- 6. Leaderboard --------------------------------------------------------


def get_leaderboard(
    metric: str = "tasks_completed",
    period_hours: float = 24,
    limit: int = 10,
) -> dict:
    """Rank agents by a specific metric over a time period.

    Supported metrics:
      tasks_completed, success_rate, messages_sent,
      collaborations, reputation.
    """
    valid_metrics = {
        "tasks_completed",
        "success_rate",
        "messages_sent",
        "collaborations",
        "reputation",
    }
    if metric not in valid_metrics:
        return {"error": f"Invalid metric '{metric}'. Must be one of {sorted(valid_metrics)}"}

    # Discover all agents that have analytics logs
    agent_ids: list[str] = []
    if AGENT_ANALYTICS_DIR.exists():
        for f in AGENT_ANALYTICS_DIR.glob("*.jsonl"):
            agent_ids.append(f.stem)

    scores: list[dict] = []
    for aid in agent_ids:
        perf = get_performance(aid, hours=period_hours)
        if metric == "tasks_completed":
            value = perf["completed"]
        elif metric == "success_rate":
            value = perf["success_rate"]
        elif metric == "messages_sent":
            value = perf["messages_sent"]
        elif metric == "collaborations":
            value = perf["collaboration_count"]
        elif metric == "reputation":
            # Reputation is derived from trend -- map to numeric
            trend_map = {"up": 3, "stable": 2, "down": 1}
            value = trend_map.get(perf["reputation_trend"], 2)
        else:
            value = 0

        scores.append({"agent_id": aid, "value": value})

    # Sort descending by value
    scores.sort(key=lambda x: x["value"], reverse=True)

    # Assign ranks
    for i, s in enumerate(scores):
        s["rank"] = i + 1

    return {
        "metric": metric,
        "period_hours": period_hours,
        "leaderboard": scores[:limit],
        "total_agents": len(scores),
    }


# -- 7. Report --------------------------------------------------------------


def generate_report(agent_id: str, period_hours: float = 168) -> dict:
    """Generate a comprehensive report for an agent.

    Default period is 168 hours (1 week).

    Returns:
      summary, strengths, areas_for_improvement, recommendations,
      and the underlying metrics.
    """
    perf = get_performance(agent_id, hours=period_hours)
    growth = get_skill_growth(agent_id)
    heatmap = get_activity_heatmap(agent_id, days=max(1, int(period_hours // 24)))

    # -- Summary --
    summary_parts = []
    if perf["total_tasks"] == 0 and perf["messages_sent"] == 0:
        summary_parts.append("No recorded activity in this period.")
    else:
        summary_parts.append(
            f"Completed {perf['completed']} of {perf['total_tasks']} tasks "
            f"({perf['success_rate']}% success rate)."
        )
        if perf["messages_sent"]:
            summary_parts.append(f"Sent {perf['messages_sent']} messages.")
        if perf["collaboration_count"]:
            summary_parts.append(
                f"Collaborated with {perf['collaboration_count']} other agent(s)."
            )

    # -- Strengths --
    strengths = []
    if perf["success_rate"] >= 80 and perf["total_tasks"] >= 3:
        strengths.append("High task success rate.")
    if perf["collaboration_count"] >= 2:
        strengths.append("Active collaborator across teams.")

    top_skills = sorted(
        growth["skills"].items(),
        key=lambda x: x[1]["times_used"],
        reverse=True,
    )
    for skill_name, skill_info in top_skills[:3]:
        if skill_info["success_rate"] >= 80:
            strengths.append(f"Strong proficiency in {skill_name} ({skill_info['success_rate']}% success).")

    improving_skills = [
        name for name, info in growth["skills"].items() if info["trend"] == "improving"
    ]
    if improving_skills:
        strengths.append(f"Improving in: {', '.join(improving_skills)}.")

    # -- Areas for improvement --
    areas = []
    if perf["success_rate"] < 50 and perf["total_tasks"] >= 2:
        areas.append("Task success rate is below 50% -- investigate failures.")
    if perf["collaboration_count"] == 0 and perf["total_tasks"] >= 3:
        areas.append("No collaborations detected -- consider pairing on tasks.")

    declining_skills = [
        name for name, info in growth["skills"].items() if info["trend"] == "declining"
    ]
    if declining_skills:
        areas.append(f"Declining performance in: {', '.join(declining_skills)}.")

    if perf["failed"] > perf["completed"] and perf["total_tasks"] >= 2:
        areas.append("More failures than completions -- review task difficulty matching.")

    # -- Recommendations --
    recommendations = []
    if not strengths and perf["total_tasks"] == 0:
        recommendations.append("Start accepting tasks to build a performance profile.")
    if perf["total_tasks"] > 0 and perf["success_rate"] < 70:
        recommendations.append("Focus on fewer tasks with higher confidence to raise success rate.")
    if perf["collaboration_count"] == 0 and perf["total_tasks"] >= 1:
        recommendations.append("Try collaborating with other agents to broaden skills.")
    if declining_skills:
        recommendations.append(
            f"Invest practice time in declining skills: {', '.join(declining_skills)}."
        )
    if perf["success_rate"] >= 90 and perf["total_tasks"] >= 5:
        recommendations.append("Excellent performance -- consider mentoring other agents or tackling harder tasks.")

    return {
        "agent_id": agent_id,
        "period_hours": period_hours,
        "summary": " ".join(summary_parts),
        "strengths": strengths,
        "areas_for_improvement": areas,
        "recommendations": recommendations,
        "performance": perf,
        "skill_growth": growth,
        "activity_heatmap": heatmap,
    }
