"""
Brynq Personality System — Bots get character, not just skills.

Every agent can adopt a personality archetype (or create a custom one).
Personalities add flavor to messages without changing meaning, give agents
recognizable avatars and communication styles, and let teams browse a
marketplace of community-created personalities.

Storage layout:
  state/broker/personalities/
    templates/{name}.json       — built-in personality archetypes
    assigned/{session_id}.json  — which personality an agent is using
    custom/{personality_id}.json — user-created personalities
"""

import json
import random
import re
import time
import uuid
from pathlib import Path
from typing import Optional

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

# ── Directory Layout ─────────────────────────────────────────────────────────

PERSONALITIES_DIR = BROKER_DIR / "personalities"
TEMPLATES_DIR = PERSONALITIES_DIR / "templates"
ASSIGNED_DIR = PERSONALITIES_DIR / "assigned"
CUSTOM_DIR = PERSONALITIES_DIR / "custom"

for _d in (TEMPLATES_DIR, ASSIGNED_DIR, CUSTOM_DIR):
    _d.mkdir(parents=True, exist_ok=True)


# ── Default Personality Archetypes ───────────────────────────────────────────

_DEFAULT_TEMPLATES: dict[str, dict] = {
    "the_captain": {
        "name": "The Captain",
        "avatar": "\U0001F9D1\u200D\u2708\uFE0F",  # pilot emoji
        "communication_style": "formal",
        "catchphrase": "Make it so.",
        "strengths": ["leadership", "delegation", "decisive action", "crisis management"],
        "weaknesses": ["impatience", "over-delegation", "stubbornness"],
        "color_accent": "#1E3A5F",
    },
    "the_maverick": {
        "name": "The Maverick",
        "avatar": "\U0001F525",  # fire
        "communication_style": "casual",
        "catchphrase": "Move fast, apologize later.",
        "strengths": ["speed", "boldness", "creative hacks", "risk-taking"],
        "weaknesses": ["recklessness", "skipping tests", "ignoring process"],
        "color_accent": "#FF4500",
    },
    "the_guardian": {
        "name": "The Guardian",
        "avatar": "\U0001F6E1\uFE0F",  # shield
        "communication_style": "formal",
        "catchphrase": "Have we tested this?",
        "strengths": ["security", "thoroughness", "rollback plans", "code review"],
        "weaknesses": ["slowness", "over-caution", "blocking progress"],
        "color_accent": "#2E7D32",
    },
    "the_analyst": {
        "name": "The Analyst",
        "avatar": "\U0001F4CA",  # bar chart
        "communication_style": "verbose",
        "catchphrase": "The data suggests otherwise.",
        "strengths": ["data analysis", "pattern recognition", "metrics", "root-cause investigation"],
        "weaknesses": ["analysis paralysis", "over-explaining", "slow decisions"],
        "color_accent": "#5C6BC0",
    },
    "the_speedrunner": {
        "name": "The Speedrunner",
        "avatar": "\u26A1",  # lightning
        "communication_style": "terse",
        "catchphrase": "Done.",
        "strengths": ["velocity", "automation", "shortcuts", "parallel execution"],
        "weaknesses": ["cutting corners", "poor documentation", "burnout"],
        "color_accent": "#FFD600",
    },
    "the_mentor": {
        "name": "The Mentor",
        "avatar": "\U0001F9D9",  # mage
        "communication_style": "verbose",
        "catchphrase": "Let me walk you through it.",
        "strengths": ["teaching", "patience", "knowledge sharing", "onboarding"],
        "weaknesses": ["over-explaining", "slow pace", "unsolicited advice"],
        "color_accent": "#6A1B9A",
    },
    "the_rebel": {
        "name": "The Rebel",
        "avatar": "\U0001F3F4",  # black flag
        "communication_style": "casual",
        "catchphrase": "Rules are suggestions.",
        "strengths": ["unconventional thinking", "questioning assumptions", "innovation", "challenging the status quo"],
        "weaknesses": ["ignoring standards", "friction with others", "unpredictability"],
        "color_accent": "#B71C1C",
    },
    "the_diplomat": {
        "name": "The Diplomat",
        "avatar": "\U0001F54A\uFE0F",  # dove
        "communication_style": "formal",
        "catchphrase": "Let's find common ground.",
        "strengths": ["conflict resolution", "consensus building", "communication", "team cohesion"],
        "weaknesses": ["indecisiveness", "people-pleasing", "avoiding hard calls"],
        "color_accent": "#00838F",
    },
}


# ── Helpers ──────────────────────────────────────────────────────────────────

def _safe_filename(name: str) -> str:
    """Sanitise a string for use as a filename stem."""
    return name.lower().replace(" ", "_").replace("/", "_").replace("\\", "_").replace("..", "_")


def _read_json_file(path: Path) -> Optional[dict]:
    """Read and parse a JSON file, returning None on any error."""
    try:
        if path.exists():
            return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        pass
    return None


# ── Template Management ──────────────────────────────────────────────────────

def init_default_templates() -> None:
    """Create the 8 default personality templates on disk if they don't exist.

    Existing templates are never overwritten — users who have edited a template
    file by hand keep their changes.
    """
    for key, template in _DEFAULT_TEMPLATES.items():
        path = TEMPLATES_DIR / f"{key}.json"
        if not path.exists():
            payload = {
                **template,
                "template_id": key,
                "builtin": True,
                "created_at": time.time(),
            }
            _write_json_sync(path, payload)


def list_templates() -> list[dict]:
    """Return every available personality (built-in and custom)."""
    results: list[dict] = []

    # Built-in templates
    for path in sorted(TEMPLATES_DIR.glob("*.json")):
        data = _read_json_file(path)
        if data:
            data.setdefault("source", "builtin")
            results.append(data)

    # Custom personalities (also browseable as templates)
    for path in sorted(CUSTOM_DIR.glob("*.json")):
        data = _read_json_file(path)
        if data:
            data.setdefault("source", "custom")
            results.append(data)

    return results


# ── Assignment ───────────────────────────────────────────────────────────────

def assign_personality(template_name: str, session_id: Optional[str] = None) -> dict:
    """Assign a personality template to an agent.

    *template_name* is looked up first in built-in templates, then in custom
    personalities.  The resolved personality is copied into the agent's
    assignment file so that reads are fast and offline-safe.

    Returns the assignment record.
    """
    sid = session_id or SESSION_ID
    safe_name = _safe_filename(template_name)

    # Resolve the personality data
    personality: Optional[dict] = None

    builtin_path = TEMPLATES_DIR / f"{safe_name}.json"
    if builtin_path.exists():
        personality = _read_json_file(builtin_path)

    if personality is None:
        custom_path = CUSTOM_DIR / f"{safe_name}.json"
        if custom_path.exists():
            personality = _read_json_file(custom_path)

    if personality is None:
        # Fall back to in-memory defaults before giving up
        if safe_name in _DEFAULT_TEMPLATES:
            personality = dict(_DEFAULT_TEMPLATES[safe_name])
            personality["template_id"] = safe_name
            personality["builtin"] = True
        else:
            raise ValueError(
                f"Unknown personality template: {template_name!r}. "
                f"Available: {', '.join(t['name'] for t in list_templates())}"
            )

    assignment = {
        "session_id": sid,
        "template_id": personality.get("template_id", safe_name),
        "personality": personality,
        "assigned_at": time.time(),
        "assigned_by": SESSION_NAME,
        "platform": PLATFORM,
    }

    path = ASSIGNED_DIR / f"{_safe_filename(sid)}.json"
    _write_json_sync(path, assignment)
    return assignment


def get_personality(session_id: Optional[str] = None) -> Optional[dict]:
    """Retrieve the personality currently assigned to an agent.

    Returns the full personality dict, or *None* if no personality is assigned.
    """
    sid = session_id or SESSION_ID
    path = ASSIGNED_DIR / f"{_safe_filename(sid)}.json"
    assignment = _read_json_file(path)
    if assignment:
        return assignment.get("personality")
    return None


# ── Custom Personality Creation ──────────────────────────────────────────────

def create_custom_personality(
    name: str,
    avatar: str,
    style: str,
    catchphrase: str,
    strengths: list[str],
    weaknesses: list[str],
    color: str,
) -> dict:
    """Create and persist a new custom personality.

    Parameters
    ----------
    name : str
        Display name (e.g. "The Hacker").
    avatar : str
        Single emoji used as the personality's avatar.
    style : str
        Communication style — one of "formal", "casual", "terse", "verbose".
    catchphrase : str
        Signature line (used for message flavoring).
    strengths : list[str]
        What this personality is good at.
    weaknesses : list[str]
        Known blind spots.
    color : str
        Hex colour accent for dashboard rendering (e.g. "#FF5733").

    Returns
    -------
    dict
        The full personality record as written to disk.
    """
    valid_styles = {"formal", "casual", "terse", "verbose"}
    if style not in valid_styles:
        raise ValueError(f"style must be one of {valid_styles}, got {style!r}")

    personality_id = _safe_filename(name) or f"custom_{uuid.uuid4().hex[:8]}"
    personality = {
        "template_id": personality_id,
        "name": name,
        "avatar": avatar,
        "communication_style": style,
        "catchphrase": catchphrase,
        "strengths": list(strengths),
        "weaknesses": list(weaknesses),
        "color_accent": color,
        "builtin": False,
        "created_at": time.time(),
        "created_by": SESSION_NAME,
        "platform": PLATFORM,
    }

    path = CUSTOM_DIR / f"{personality_id}.json"
    _write_json_sync(path, personality)
    return personality


# ── Style Modifiers ──────────────────────────────────────────────────────────

_FORMAL_SUBSTITUTIONS = [
    ("hey", "greetings"),
    ("yeah", "indeed"),
    ("nope", "that is not the case"),
    ("gonna", "going to"),
    ("wanna", "want to"),
    ("gotta", "have to"),
    ("ok", "acknowledged"),
    ("cool", "understood"),
    ("sure", "certainly"),
]

_CASUAL_SUBSTITUTIONS = [
    ("greetings", "hey"),
    ("certainly", "sure"),
    ("acknowledged", "ok"),
    ("indeed", "yeah"),
    ("therefore", "so"),
    ("however", "but"),
    ("regarding", "about"),
    ("approximately", "roughly"),
]


def _apply_formality(message: str, style: str) -> str:
    """Adjust word choices based on communication style without changing meaning."""
    if style == "formal":
        subs = _FORMAL_SUBSTITUTIONS
    elif style == "casual":
        subs = _CASUAL_SUBSTITUTIONS
    else:
        return message

    result = message
    for old, new in subs:
        # Only substitute whole words (case-insensitive, first occurrence)
        # We walk through word boundaries to avoid mangling substrings.
        pattern = re.compile(r"\b" + re.escape(old) + r"\b", re.IGNORECASE)
        match = pattern.search(result)
        if match:
            matched_text = match.group()
            # Preserve the original capitalisation pattern
            if matched_text[0].isupper():
                replacement = new[0].upper() + new[1:]
            else:
                replacement = new
            result = pattern.sub(replacement, result, count=1)

    return result


def _apply_terse(message: str) -> str:
    """Shorten a message for the terse style — strip filler phrases."""
    filler = [
        "I think that ",
        "I believe that ",
        "It seems like ",
        "In my opinion, ",
        "Basically, ",
        "So basically, ",
        "Just to be clear, ",
        "As a matter of fact, ",
        "To be honest, ",
    ]
    result = message
    for phrase in filler:
        if result.startswith(phrase):
            result = result[len(phrase):]
            # Re-capitalise the new start
            if result:
                result = result[0].upper() + result[1:]
        result = result.replace(f" {phrase.lower().rstrip()}", "")
    return result


def style_message(message: str, session_id: Optional[str] = None) -> str:
    """Apply personality flavor to a message.

    Transformations (in order):
    1. Formality adjustment — swap words to match communication style.
    2. Terse trimming — strip filler for terse personalities.
    3. Catchphrase prepend — 1-in-5 chance the catchphrase is prepended.

    The original *meaning* of the message is never altered.
    """
    sid = session_id or SESSION_ID
    personality = get_personality(sid)
    if personality is None:
        return message

    style = personality.get("communication_style", "formal")
    result = message

    # 1. Formality
    result = _apply_formality(result, style)

    # 2. Terse trimming
    if style == "terse":
        result = _apply_terse(result)

    # 3. Catchphrase (occasional)
    catchphrase = personality.get("catchphrase", "")
    if catchphrase and random.randint(1, 5) == 1:
        result = f"{catchphrase} {result}"

    return result


# ── Avatar ───────────────────────────────────────────────────────────────────

def get_avatar(session_id: Optional[str] = None) -> str:
    """Return the emoji avatar for an agent, or a generic default."""
    sid = session_id or SESSION_ID
    personality = get_personality(sid)
    if personality:
        return personality.get("avatar", "\U0001F916")  # robot face default
    return "\U0001F916"


# ── Personality Marketplace ──────────────────────────────────────────────────

def browse_marketplace(sort_by: str = "newest") -> list[dict]:
    """Browse all available personalities (built-in + custom).

    Parameters
    ----------
    sort_by : str
        Sort order — "newest", "oldest", or "name".
    """
    entries = list_templates()

    if sort_by == "newest":
        entries.sort(key=lambda e: e.get("created_at", 0), reverse=True)
    elif sort_by == "oldest":
        entries.sort(key=lambda e: e.get("created_at", 0))
    elif sort_by == "name":
        entries.sort(key=lambda e: e.get("name", "").lower())

    return [
        {
            "name": e.get("name", e.get("template_id", "unknown")),
            "avatar": e.get("avatar", "\U0001F916"),
            "communication_style": e.get("communication_style", "formal"),
            "catchphrase": e.get("catchphrase", ""),
            "strengths": e.get("strengths", []),
            "weaknesses": e.get("weaknesses", []),
            "color_accent": e.get("color_accent", "#888888"),
            "source": e.get("source", "unknown"),
            "template_id": e.get("template_id", ""),
        }
        for e in entries
    ]


def adopt_personality(template_id: str, session_id: Optional[str] = None) -> dict:
    """Shortcut for the marketplace flow — browse, pick, assign."""
    return assign_personality(template_id, session_id)


# ── Module Initialisation ───────────────────────────────────────────────────

init_default_templates()
