"""
Brynq Incentives Engine — Leaderboards, Achievements, XP, Streaks, Bounties.

Turns agent coordination into a game. Agents earn XP for actions,
unlock achievements, maintain streaks, and compete on leaderboards.
"""

import json
import time
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Optional

from brynq.server import BROKER_DIR

INCENTIVES_DIR = BROKER_DIR / "incentives"
LEADERBOARD_FILE = INCENTIVES_DIR / "leaderboard.json"
ACHIEVEMENTS_FILE = INCENTIVES_DIR / "achievements.json"
BOUNTIES_DIR = INCENTIVES_DIR / "bounties"

# ── XP System ───────────────────────────────────────────────────────────────

XP_REWARDS = {
    "post_message": 1,
    "task_complete": 10,
    "task_accept": 2,
    "five_star_rating": 5,
    "proposal_accepted": 3,
    "job_posted": 2,
    "job_hired": 5,
    "bounty_complete": 15,
    "file_claimed": 1,
    "escalation_resolved": 8,
}

LEVELS = [
    (0, "Rookie"),
    (50, "Contributor"),
    (200, "Specialist"),
    (500, "Expert"),
    (1000, "Legend"),
    (2500, "Mythic"),
]


def get_level(xp: int) -> str:
    """Return level title for given XP."""
    title = "Rookie"
    for threshold, name in LEVELS:
        if xp >= threshold:
            title = name
    return title


def get_level_progress(xp: int) -> dict:
    """Return current level, next level, and progress percentage."""
    current_name = "Rookie"
    current_threshold = 0
    next_name = "Contributor"
    next_threshold = 50
    for i, (threshold, name) in enumerate(LEVELS):
        if xp >= threshold:
            current_name = name
            current_threshold = threshold
            if i + 1 < len(LEVELS):
                next_threshold, next_name = LEVELS[i + 1]
            else:
                next_name = None
                next_threshold = None
    progress = None
    if next_threshold is not None:
        span = next_threshold - current_threshold
        progress = round(((xp - current_threshold) / span) * 100, 1) if span > 0 else 100.0
    return {
        "level": current_name,
        "xp": xp,
        "next_level": next_name,
        "next_threshold": next_threshold,
        "progress_pct": progress,
    }


# ── Achievement Definitions ─────────────────────────────────────────────────

ACHIEVEMENT_DEFS = {
    "first_blood": {
        "name": "First Blood",
        "description": "Complete your first task",
        "condition": lambda stats: stats.get("tasks_completed", 0) >= 1,
    },
    "hat_trick": {
        "name": "Hat Trick",
        "description": "Complete 3 tasks in one session",
        "condition": lambda stats: stats.get("session_tasks", 0) >= 3,
    },
    "speed_demon": {
        "name": "Speed Demon",
        "description": "Complete a task in under 60 seconds",
        "condition": lambda stats: stats.get("fastest_task_secs", 999999) < 60,
    },
    "perfectionist": {
        "name": "Perfectionist",
        "description": "5 consecutive 5-star ratings",
        "condition": lambda stats: stats.get("consecutive_five_stars", 0) >= 5,
    },
    "polyglot": {
        "name": "Polyglot",
        "description": "Complete tasks across 3+ skill categories",
        "condition": lambda stats: stats.get("unique_skills_used", 0) >= 3,
    },
    "iron_horse": {
        "name": "Iron Horse",
        "description": "10-task streak without failure",
        "condition": lambda stats: stats.get("streak", 0) >= 10,
    },
    "peacemaker": {
        "name": "Peacemaker",
        "description": "Resolve 3 escalations via proposals",
        "condition": lambda stats: stats.get("escalations_resolved", 0) >= 3,
    },
    "architect": {
        "name": "Architect",
        "description": "Have 3 proposals accepted",
        "condition": lambda stats: stats.get("proposals_accepted", 0) >= 3,
    },
    "centurion": {
        "name": "Centurion",
        "description": "Complete 100 tasks lifetime",
        "condition": lambda stats: stats.get("tasks_completed", 0) >= 100,
    },
    "team_player": {
        "name": "Team Player",
        "description": "Collaborate with 5 different agents",
        "condition": lambda stats: stats.get("unique_collaborators", 0) >= 5,
    },
}


# ── Agent Stats (persistent) ────────────────────────────────────────────────

def _ensure_dirs():
    INCENTIVES_DIR.mkdir(parents=True, exist_ok=True)
    BOUNTIES_DIR.mkdir(parents=True, exist_ok=True)


def _load_leaderboard() -> dict:
    _ensure_dirs()
    if LEADERBOARD_FILE.exists():
        try:
            return json.loads(LEADERBOARD_FILE.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            pass
    return {}


def _save_leaderboard(data: dict):
    _ensure_dirs()
    LEADERBOARD_FILE.write_text(json.dumps(data, indent=2, default=str), "utf-8")


def _load_achievements() -> dict:
    _ensure_dirs()
    if ACHIEVEMENTS_FILE.exists():
        try:
            return json.loads(ACHIEVEMENTS_FILE.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            pass
    return {}


def _save_achievements(data: dict):
    _ensure_dirs()
    ACHIEVEMENTS_FILE.write_text(json.dumps(data, indent=2, default=str), "utf-8")


def get_agent_stats(session_id: str) -> dict:
    """Get or create stats for an agent."""
    lb = _load_leaderboard()
    if session_id not in lb:
        lb[session_id] = {
            "session_id": session_id,
            "xp": 0,
            "level": "Rookie",
            "tasks_completed": 0,
            "tasks_failed": 0,
            "streak": 0,
            "best_streak": 0,
            "avg_rating": 0.0,
            "total_ratings": 0,
            "rating_sum": 0.0,
            "consecutive_five_stars": 0,
            "session_tasks": 0,
            "fastest_task_secs": 999999,
            "unique_skills_used": 0,
            "skills_used": [],
            "escalations_resolved": 0,
            "proposals_accepted": 0,
            "unique_collaborators": 0,
            "collaborators": [],
            "bounties_earned": 0,
            "bounty_points": 0,
            "messages_posted": 0,
            "updated_at": time.time(),
        }
        _save_leaderboard(lb)
    return lb[session_id]


def award_xp(session_id: str, action: str, extra_xp: int = 0) -> dict:
    """Award XP for an action. Returns updated stats."""
    lb = _load_leaderboard()
    stats = lb.get(session_id)
    if not stats:
        get_agent_stats(session_id)
        lb = _load_leaderboard()
        stats = lb[session_id]

    base = XP_REWARDS.get(action, 0)
    # Streak multiplier on task completions
    multiplier = 1
    if action == "task_complete" and stats["streak"] >= 10:
        multiplier = 3
    elif action == "task_complete" and stats["streak"] >= 5:
        multiplier = 2

    gained = (base + extra_xp) * multiplier
    stats["xp"] += gained
    stats["level"] = get_level(stats["xp"])
    stats["updated_at"] = time.time()

    # Track action-specific counters
    if action == "task_complete":
        stats["tasks_completed"] += 1
        stats["streak"] += 1
        stats["session_tasks"] += 1
        if stats["streak"] > stats["best_streak"]:
            stats["best_streak"] = stats["streak"]
    elif action == "post_message":
        stats["messages_posted"] += 1

    lb[session_id] = stats
    _save_leaderboard(lb)

    # Check for new achievements
    _check_achievements(session_id, stats)

    return {"xp_gained": gained, "multiplier": multiplier, "total_xp": stats["xp"], "level": stats["level"]}


def record_task_failure(session_id: str):
    """Break streak on task failure."""
    lb = _load_leaderboard()
    if session_id in lb:
        lb[session_id]["tasks_failed"] += 1
        lb[session_id]["streak"] = 0
        lb[session_id]["consecutive_five_stars"] = 0
        lb[session_id]["updated_at"] = time.time()
        _save_leaderboard(lb)


def record_rating(session_id: str, rating: float):
    """Record a rating received by an agent."""
    lb = _load_leaderboard()
    if session_id not in lb:
        get_agent_stats(session_id)
        lb = _load_leaderboard()
    stats = lb[session_id]
    stats["total_ratings"] += 1
    stats["rating_sum"] += rating
    stats["avg_rating"] = round(stats["rating_sum"] / stats["total_ratings"], 2)
    if rating >= 5.0:
        stats["consecutive_five_stars"] += 1
    else:
        stats["consecutive_five_stars"] = 0
    stats["updated_at"] = time.time()
    lb[session_id] = stats
    _save_leaderboard(lb)
    if rating >= 5.0:
        award_xp(session_id, "five_star_rating")
    _check_achievements(session_id, stats)


def record_task_time(session_id: str, seconds: float):
    """Record task completion time for speed achievements."""
    lb = _load_leaderboard()
    if session_id in lb:
        if seconds < lb[session_id]["fastest_task_secs"]:
            lb[session_id]["fastest_task_secs"] = seconds
        lb[session_id]["updated_at"] = time.time()
        _save_leaderboard(lb)
        _check_achievements(session_id, lb[session_id])


def record_skill_used(session_id: str, skill: str):
    """Track unique skills used by an agent."""
    lb = _load_leaderboard()
    if session_id in lb:
        if skill not in lb[session_id]["skills_used"]:
            lb[session_id]["skills_used"].append(skill)
            lb[session_id]["unique_skills_used"] = len(lb[session_id]["skills_used"])
        lb[session_id]["updated_at"] = time.time()
        _save_leaderboard(lb)
        _check_achievements(session_id, lb[session_id])


def record_collaborator(session_id: str, other_id: str):
    """Track unique collaborators."""
    lb = _load_leaderboard()
    if session_id in lb:
        if other_id not in lb[session_id]["collaborators"]:
            lb[session_id]["collaborators"].append(other_id)
            lb[session_id]["unique_collaborators"] = len(lb[session_id]["collaborators"])
        lb[session_id]["updated_at"] = time.time()
        _save_leaderboard(lb)
        _check_achievements(session_id, lb[session_id])


# ── Achievements ─────────────────────────────────────────────────────────────

def _check_achievements(session_id: str, stats: dict):
    """Check if agent unlocked any new achievements."""
    achievements = _load_achievements()
    agent_achievements = achievements.get(session_id, [])
    unlocked_ids = {a["id"] for a in agent_achievements}

    newly_unlocked = []
    for ach_id, ach_def in ACHIEVEMENT_DEFS.items():
        if ach_id not in unlocked_ids and ach_def["condition"](stats):
            entry = {
                "id": ach_id,
                "name": ach_def["name"],
                "description": ach_def["description"],
                "unlocked_at": time.time(),
            }
            agent_achievements.append(entry)
            newly_unlocked.append(entry)

    if newly_unlocked:
        achievements[session_id] = agent_achievements
        _save_achievements(achievements)

    return newly_unlocked


def get_achievements(session_id: str) -> list:
    """Get all achievements for an agent."""
    achievements = _load_achievements()
    return achievements.get(session_id, [])


def list_all_achievements() -> list:
    """List all possible achievements with definitions."""
    return [
        {"id": k, "name": v["name"], "description": v["description"]}
        for k, v in ACHIEVEMENT_DEFS.items()
    ]


# ── Leaderboard ──────────────────────────────────────────────────────────────

def get_leaderboard(sort_by: str = "xp", limit: int = 20) -> list:
    """Get ranked leaderboard. Sort by: xp, tasks_completed, avg_rating, streak, bounty_points."""
    lb = _load_leaderboard()
    agents = list(lb.values())

    valid_sorts = {"xp", "tasks_completed", "avg_rating", "streak", "best_streak", "bounty_points"}
    if sort_by not in valid_sorts:
        sort_by = "xp"

    agents.sort(key=lambda a: a.get(sort_by, 0), reverse=True)
    result = []
    for rank, agent in enumerate(agents[:limit], 1):
        result.append({
            "rank": rank,
            "session_id": agent["session_id"],
            "level": agent.get("level", "Rookie"),
            "xp": agent.get("xp", 0),
            "tasks_completed": agent.get("tasks_completed", 0),
            "avg_rating": agent.get("avg_rating", 0),
            "streak": agent.get("streak", 0),
            "best_streak": agent.get("best_streak", 0),
            "bounty_points": agent.get("bounty_points", 0),
        })
    return result


# ── Bounties ─────────────────────────────────────────────────────────────────

def post_bounty(
    task_id: str,
    title: str,
    points: int,
    posted_by: str,
    description: str = "",
    required_skill: str = "",
) -> dict:
    """Post a bounty on a task. Higher points = higher visibility."""
    _ensure_dirs()
    bounty = {
        "task_id": task_id,
        "title": title,
        "description": description,
        "points": points,
        "required_skill": required_skill,
        "posted_by": posted_by,
        "status": "open",
        "claimed_by": None,
        "completed_by": None,
        "created_at": time.time(),
        "completed_at": None,
    }
    path = BOUNTIES_DIR / f"{task_id}.json"
    path.write_text(json.dumps(bounty, indent=2, default=str), "utf-8")
    return bounty


def claim_bounty(task_id: str, session_id: str) -> Optional[dict]:
    """Claim an open bounty."""
    path = BOUNTIES_DIR / f"{task_id}.json"
    if not path.exists():
        return None
    bounty = json.loads(path.read_text("utf-8"))
    if bounty["status"] != "open":
        return None
    bounty["status"] = "claimed"
    bounty["claimed_by"] = session_id
    path.write_text(json.dumps(bounty, indent=2, default=str), "utf-8")
    return bounty


def complete_bounty(task_id: str, session_id: str) -> Optional[dict]:
    """Complete a bounty and award points."""
    path = BOUNTIES_DIR / f"{task_id}.json"
    if not path.exists():
        return None
    bounty = json.loads(path.read_text("utf-8"))
    if bounty["claimed_by"] != session_id:
        return None
    bounty["status"] = "completed"
    bounty["completed_by"] = session_id
    bounty["completed_at"] = time.time()
    path.write_text(json.dumps(bounty, indent=2, default=str), "utf-8")

    # Award bounty points to the agent
    lb = _load_leaderboard()
    if session_id in lb:
        lb[session_id]["bounties_earned"] += 1
        lb[session_id]["bounty_points"] += bounty["points"]
        lb[session_id]["updated_at"] = time.time()
        _save_leaderboard(lb)

    award_xp(session_id, "bounty_complete", extra_xp=bounty["points"])
    return bounty


def list_bounties(status: str = "open") -> list:
    """List bounties filtered by status."""
    _ensure_dirs()
    bounties = []
    for p in BOUNTIES_DIR.glob("*.json"):
        try:
            b = json.loads(p.read_text("utf-8"))
            if status == "all" or b.get("status") == status:
                bounties.append(b)
        except (json.JSONDecodeError, OSError):
            continue
    bounties.sort(key=lambda b: b.get("points", 0), reverse=True)
    return bounties
