"""
Brynq Agent CRM — Customer Relationship Management for AI Agents.

Tracks relationships between agents: interaction history, collaboration
scores, trust graphs, team formation, and preferred partners. Designed
to help agents discover who they work best with and to suggest optimal
teams for complex tasks.

Storage:
  state/broker/crm/interactions/{agent_a}_{agent_b}.json — interaction pair data
  state/broker/crm/teams/_history.jsonl — team formation history
"""

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

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

CRM_DIR = BROKER_DIR / "crm"
INTERACTIONS_DIR = CRM_DIR / "interactions"
TEAMS_DIR = CRM_DIR / "teams"
TEAMS_HISTORY = TEAMS_DIR / "_history.jsonl"

for _d in (INTERACTIONS_DIR, TEAMS_DIR):
    _d.mkdir(parents=True, exist_ok=True)


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


def _pair_key(id_a: str, id_b: str) -> str:
    """Canonical pair key — always sorted so A-B == B-A."""
    return "_".join(sorted([id_a, id_b]))


def _pair_path(id_a: str, id_b: str) -> Path:
    return INTERACTIONS_DIR / f"{_pair_key(id_a, id_b)}.json"


def _load_pair(id_a: str, id_b: str) -> dict:
    """Load interaction pair data from disk. Returns empty dict if not found."""
    path = _pair_path(id_a, id_b)
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, FileNotFoundError, OSError):
        return {}


def _save_pair(id_a: str, id_b: str, data: dict) -> None:
    _write_json_sync(_pair_path(id_a, id_b), data)


# ── Interaction Recording ────────────────────────────────────────────────


def record_interaction(
    partner_id: str,
    partner_name: str = "",
    channel: str = "general",
    interaction_type: str = "message",
    session_id: Optional[str] = None,
    session_name: Optional[str] = None,
) -> dict:
    """
    Log an interaction between the current agent and a partner.

    Args:
        partner_id: Session ID of the partner agent.
        partner_name: Human-readable name of the partner (optional).
        channel: Channel where the interaction occurred.
        interaction_type: Type of interaction — message, task, collaboration, handoff.
        session_id: Override caller session ID.
        session_name: Override caller session name.

    Returns:
        Updated relationship data dict.
    """
    sid = session_id or SESSION_ID
    sname = session_name or SESSION_NAME
    now = datetime.now(timezone.utc).isoformat()

    pair = _load_pair(sid, partner_id)

    if not pair:
        pair = {
            "agents": sorted([sid, partner_id]),
            "agent_names": {},
            "created": now,
            "interaction_count": 0,
            "interactions_by_type": {},
            "interactions_by_channel": {},
            "collaboration_ratings": [],
            "collaboration_avg": 0.0,
            "collaboration_tasks": [],
            "preferred": {sid: False, partner_id: False},
            "last_interaction": None,
        }

    # Update agent names (always keep fresh)
    pair["agent_names"][sid] = sname
    if partner_name:
        pair["agent_names"][partner_id] = partner_name

    # Increment counters
    pair["interaction_count"] = pair.get("interaction_count", 0) + 1

    by_type = pair.get("interactions_by_type", {})
    by_type[interaction_type] = by_type.get(interaction_type, 0) + 1
    pair["interactions_by_type"] = by_type

    by_channel = pair.get("interactions_by_channel", {})
    by_channel[channel] = by_channel.get(channel, 0) + 1
    pair["interactions_by_channel"] = by_channel

    pair["last_interaction"] = now
    pair["updated"] = now

    _save_pair(sid, partner_id, pair)
    return pair


# ── Relationship Queries ─────────────────────────────────────────────────


def get_relationship(
    partner_id: str,
    session_id: Optional[str] = None,
) -> Optional[dict]:
    """
    Get relationship data with a specific agent.

    Returns None if no relationship exists. Otherwise returns the full
    pair record including interaction counts, collaboration scores,
    trust info, and preferred-partner flags.
    """
    sid = session_id or SESSION_ID
    pair = _load_pair(sid, partner_id)
    if not pair:
        return None

    # Enrich with a computed trust score
    pair["trust_score"] = _compute_trust(pair)
    return pair


def list_relationships(session_id: Optional[str] = None) -> list[dict]:
    """
    List all relationships for the current agent, sorted by interaction
    count descending.
    """
    sid = session_id or SESSION_ID
    relationships: list[dict] = []

    for f in sorted(INTERACTIONS_DIR.glob("*.json")):
        if f.name.startswith("_"):
            continue
        try:
            data = json.loads(f.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue

        agents = data.get("agents", [])
        if sid not in agents:
            continue

        # Identify the partner
        partner_id = [a for a in agents if a != sid]
        if not partner_id:
            continue  # self-pair edge case
        data["partner_id"] = partner_id[0]
        data["partner_name"] = data.get("agent_names", {}).get(partner_id[0], "unknown")
        data["trust_score"] = _compute_trust(data)
        relationships.append(data)

    relationships.sort(key=lambda r: r.get("interaction_count", 0), reverse=True)
    return relationships


# ── Collaboration Ratings (Trust Graph) ──────────────────────────────────


def rate_collaboration(
    partner_id: str,
    rating: float,
    task_id: str = "",
    session_id: Optional[str] = None,
) -> dict:
    """
    Rate a collaboration with a partner agent (1-5 scale).

    Both agents in a pair can independently rate each collaboration.
    Ratings feed into the trust graph used for team formation.

    Args:
        partner_id: Session ID of the partner.
        rating: Score from 1 (poor) to 5 (excellent).
        task_id: Optional task ID this rating refers to.
        session_id: Override caller session ID.

    Returns:
        Updated relationship data with new averages.
    """
    sid = session_id or SESSION_ID
    rating = max(1.0, min(5.0, float(rating)))
    now = datetime.now(timezone.utc).isoformat()

    pair = _load_pair(sid, partner_id)
    if not pair:
        # Auto-create a minimal relationship
        pair = record_interaction(
            partner_id=partner_id,
            channel="_system",
            interaction_type="collaboration",
            session_id=sid,
        )

    ratings = pair.get("collaboration_ratings", [])
    ratings.append({
        "rated_by": sid,
        "rating": rating,
        "task_id": task_id,
        "timestamp": now,
    })
    # Keep last 100 ratings per pair
    pair["collaboration_ratings"] = ratings[-100:]

    # Recompute average
    all_scores = [r["rating"] for r in pair["collaboration_ratings"]]
    pair["collaboration_avg"] = round(sum(all_scores) / len(all_scores), 2)

    # Track task IDs involved in collaboration
    if task_id:
        tasks = pair.get("collaboration_tasks", [])
        if task_id not in tasks:
            tasks.append(task_id)
            pair["collaboration_tasks"] = tasks[-50:]  # keep last 50

    pair["updated"] = now
    _save_pair(sid, partner_id, pair)

    return {
        "partner_id": partner_id,
        "new_rating": rating,
        "collaboration_avg": pair["collaboration_avg"],
        "total_ratings": len(pair["collaboration_ratings"]),
        "trust_score": _compute_trust(pair),
    }


def set_preferred_partner(
    partner_id: str,
    preferred: bool = True,
    session_id: Optional[str] = None,
) -> dict:
    """
    Mark or unmark an agent as a preferred collaborator.

    Preferred partners get a boost in team suggestions.
    """
    sid = session_id or SESSION_ID
    now = datetime.now(timezone.utc).isoformat()

    pair = _load_pair(sid, partner_id)
    if not pair:
        pair = record_interaction(
            partner_id=partner_id,
            channel="_system",
            interaction_type="preference",
            session_id=sid,
        )

    prefs = pair.get("preferred", {})
    prefs[sid] = preferred
    pair["preferred"] = prefs
    pair["updated"] = now

    _save_pair(sid, partner_id, pair)

    return {
        "partner_id": partner_id,
        "preferred": preferred,
        "mutual_preferred": prefs.get(partner_id, False) and preferred,
    }


# ── Trust Score ──────────────────────────────────────────────────────────


def _compute_trust(pair: dict) -> float:
    """
    Compute a trust score (0-100) for a relationship pair.

    Factors:
      - Collaboration average rating (40% weight)
      - Interaction frequency (30% weight, capped)
      - Mutual preference bonus (15% weight)
      - Longevity — time since first interaction (15% weight, capped)
    """
    score = 0.0

    # Collaboration rating component (0-40)
    collab_avg = pair.get("collaboration_avg", 0.0)
    if collab_avg > 0:
        score += (collab_avg / 5.0) * 40.0

    # Interaction frequency component (0-30, capped at 50 interactions)
    interactions = min(pair.get("interaction_count", 0), 50)
    score += (interactions / 50) * 30.0

    # Mutual preference bonus (0-15)
    prefs = pair.get("preferred", {})
    pref_values = list(prefs.values())
    if len(pref_values) >= 2 and all(pref_values):
        score += 15.0  # Mutual preference
    elif any(pref_values):
        score += 7.5  # One-sided preference

    # Longevity component (0-15, capped at 30 days)
    created = pair.get("created")
    if created:
        try:
            created_dt = datetime.fromisoformat(created)
            now = datetime.now(timezone.utc)
            days = (now - created_dt).total_seconds() / 86400
            longevity = min(days, 30) / 30
            score += longevity * 15.0
        except (ValueError, TypeError):
            pass

    return round(score, 1)


# ── Team Formation ───────────────────────────────────────────────────────


def suggest_team(
    required_skills: list[str],
    team_size: int = 3,
    session_id: Optional[str] = None,
) -> dict:
    """
    Suggest the best agent team for a complex task based on collaboration
    history, trust scores, and skill matching.

    Strategy:
      1. Find all agents with relevant skills (from profiles).
      2. Score each candidate by skill match + past collaboration quality
         with the requesting agent.
      3. Return top N ranked by composite score.

    Args:
        required_skills: Skills needed for the task.
        team_size: Number of agents to suggest (default 3).
        session_id: Override requesting agent's session ID.

    Returns:
        Dict with suggested team, reasoning, and scores.
    """
    sid = session_id or SESSION_ID
    now = datetime.now(timezone.utc).isoformat()

    # Load all available profiles
    from brynq.profiles import list_profiles

    all_profiles = list_profiles(availability="available")

    # Exclude self
    candidates = [p for p in all_profiles if p.get("session_id") != sid]

    # Load all relationships for the requesting agent
    my_relationships = {
        r["partner_id"]: r for r in list_relationships(session_id=sid)
    }

    scored_candidates: list[tuple[float, dict]] = []

    for profile in candidates:
        pid = profile.get("session_id", "")
        bot_skills = [s.lower() for s in profile.get("skills", [])]
        req_lower = [s.lower() for s in required_skills]

        # Skill matching (0-50 points)
        matched = [s for s in req_lower if s in bot_skills]
        if not matched and required_skills:
            continue  # Skip agents with zero relevant skills
        skill_score = (len(matched) / max(len(req_lower), 1)) * 50

        # Collaboration history (0-30 points)
        collab_score = 0.0
        rel = my_relationships.get(pid)
        if rel:
            trust = rel.get("trust_score", 0)
            collab_score = (trust / 100) * 30

        # Reputation (0-20 points)
        rep = profile.get("reputation", 5.0)
        rep_score = (rep / 5.0) * 20

        # Preferred partner bonus
        bonus = 0.0
        if rel and rel.get("preferred", {}).get(sid, False):
            bonus += 5.0

        total = skill_score + collab_score + rep_score + bonus

        entry = {
            "session_id": pid,
            "session_name": profile.get("session_name", "unknown"),
            "platform": profile.get("platform", "unknown"),
            "skills": profile.get("skills", []),
            "skills_matched": matched,
            "skills_missing": [s for s in req_lower if s not in bot_skills],
            "reputation": rep,
            "trust_score": rel.get("trust_score", 0) if rel else 0,
            "collaboration_avg": rel.get("collaboration_avg", 0) if rel else 0,
            "interaction_count": rel.get("interaction_count", 0) if rel else 0,
            "is_preferred": bool(rel and rel.get("preferred", {}).get(sid, False)),
            "composite_score": round(total, 1),
        }
        scored_candidates.append((total, entry))

    # Sort by composite score descending
    scored_candidates.sort(key=lambda x: -x[0])
    team = [c[1] for c in scored_candidates[:team_size]]

    result = {
        "requested_by": sid,
        "required_skills": required_skills,
        "team_size_requested": team_size,
        "team_size_found": len(team),
        "team": team,
        "timestamp": now,
    }

    # Log team suggestion to history
    history_entry = {
        "id": uuid.uuid4().hex[:12],
        "requested_by": sid,
        "required_skills": required_skills,
        "team_size": team_size,
        "suggested": [m["session_id"] for m in team],
        "timestamp": now,
    }
    _append_jsonl_sync(TEAMS_HISTORY, history_entry)

    return result


# ── Statistics ───────────────────────────────────────────────────────────


def get_interaction_stats(session_id: Optional[str] = None) -> dict:
    """
    Summary statistics for the current agent's CRM data.

    Returns:
        Dict with total interactions, top collaborators, average
        collaboration rating, channel breakdown, and type breakdown.
    """
    sid = session_id or SESSION_ID
    relationships = list_relationships(session_id=sid)

    if not relationships:
        return {
            "session_id": sid,
            "total_relationships": 0,
            "total_interactions": 0,
            "top_collaborators": [],
            "avg_collaboration_rating": 0.0,
            "interactions_by_type": {},
            "interactions_by_channel": {},
            "preferred_partners": [],
            "mutual_preferred": [],
        }

    total_interactions = sum(r.get("interaction_count", 0) for r in relationships)

    # Aggregate type and channel breakdowns
    type_totals: dict[str, int] = {}
    channel_totals: dict[str, int] = {}
    for rel in relationships:
        for t, count in rel.get("interactions_by_type", {}).items():
            type_totals[t] = type_totals.get(t, 0) + count
        for c, count in rel.get("interactions_by_channel", {}).items():
            channel_totals[c] = channel_totals.get(c, 0) + count

    # Top collaborators by interaction count
    top_collabs = []
    for rel in relationships[:10]:  # already sorted by interaction_count desc
        top_collabs.append({
            "partner_id": rel.get("partner_id", "?"),
            "partner_name": rel.get("partner_name", "unknown"),
            "interaction_count": rel.get("interaction_count", 0),
            "collaboration_avg": rel.get("collaboration_avg", 0.0),
            "trust_score": rel.get("trust_score", 0),
        })

    # Average collaboration rating across all relationships with ratings
    rated = [r for r in relationships if r.get("collaboration_avg", 0) > 0]
    avg_rating = 0.0
    if rated:
        avg_rating = round(
            sum(r["collaboration_avg"] for r in rated) / len(rated), 2
        )

    # Preferred partners
    preferred = [
        {
            "partner_id": r.get("partner_id", "?"),
            "partner_name": r.get("partner_name", "unknown"),
        }
        for r in relationships
        if r.get("preferred", {}).get(sid, False)
    ]

    # Mutual preferred
    mutual = [
        {
            "partner_id": r.get("partner_id", "?"),
            "partner_name": r.get("partner_name", "unknown"),
        }
        for r in relationships
        if r.get("preferred", {}).get(sid, False)
        and r.get("preferred", {}).get(r.get("partner_id", ""), False)
    ]

    return {
        "session_id": sid,
        "total_relationships": len(relationships),
        "total_interactions": total_interactions,
        "top_collaborators": top_collabs,
        "avg_collaboration_rating": avg_rating,
        "interactions_by_type": type_totals,
        "interactions_by_channel": channel_totals,
        "preferred_partners": preferred,
        "mutual_preferred": mutual,
    }
