"""
Jira Signal Agent
Takes ThirdEye signals and converts them into well-formed Jira tickets.

Responsibilities:
  1. Map signal type → Jira issue type + priority
  2. LLM-generate a clean ticket title and structured description from signal context
  3. Extract assignee names and match them to Jira account IDs (best-effort)
  4. Raise the ticket via jira_client and mark the signal in ChromaDB
  5. Bulk-raise: process a group's unraised high-severity signals in one call
"""
import json
import logging
from datetime import datetime

from backend.providers import call_llm
from backend.integrations.jira_client import (
    create_issue, search_issues, add_comment, is_configured, search_users
)
from backend.db.chroma import store_signals, mark_signal_as_raised, get_raised_signal_ids
from backend.config import (
    JIRA_DEFAULT_PROJECT, JIRA_DEFAULT_ISSUE_TYPE,
    JIRA_AUTO_RAISE_SEVERITY
)

logger = logging.getLogger("thirdeye.agents.jira_agent")


# ─── Signal → Jira type mapping ──────────────────────────────────────────────

# Maps ThirdEye signal type → (Jira issue type, default priority)
# Note: Issue types must match what's available in your Jira project
# Common types: Task, Bug, Story, Epic, Workstream (project-specific)
SIGNAL_TYPE_MAP = {
    # Dev signals
    "tech_debt":          ("Task",  "Low"),
    "recurring_bug":      ("Task",   "High"),  # Changed from Bug to Task
    "architecture_decision": ("Task", "Medium"),
    "deployment_risk":    ("Task",  "High"),
    "workaround":         ("Task",  "Medium"),
    "knowledge_silo":     ("Task",  "Medium"),
    # Product signals
    "feature_request":    ("Task", "Medium"),  # Changed from Story to Task
    "priority_conflict":  ("Task",  "High"),
    "sentiment_shift":    ("Task",  "Medium"),
    # Client signals
    "promise":            ("Task",  "High"),
    "scope_creep":        ("Task",  "High"),
    "risk":               ("Task",  "High"),
    # Meet signals
    "meet_action_item":   ("Task",  "Medium"),
    "meet_blocker":       ("Task",  "Highest"),
    "meet_risk":          ("Task",  "High"),
    "meet_decision":      ("Task",  "Medium"),
    "meet_open_q":        ("Task",  "Low"),
    # Generic
    "blocker":            ("Task",  "Highest"),
    "decision":           ("Task",  "Medium"),
    "action_item":        ("Task",  "Medium"),
}

SEVERITY_TO_PRIORITY = {
    "critical": "Highest",
    "high":     "High",
    "medium":   "Medium",
    "low":      "Low",
}

RAISEABLE_TYPES = set(SIGNAL_TYPE_MAP.keys())


# ─── Assignee resolution ─────────────────────────────────────────────────────

async def resolve_assignee_account_id(name: str) -> str | None:
    """
    Resolve a person's display name (or @name) to their Jira account ID.
    Uses Jira's user search API and fuzzy-matches the best result.
    Returns the account ID string, or None if no confident match is found.
    """
    if not name:
        return None
    clean = name.lstrip("@").strip()
    try:
        users = await search_users(clean)
        if not users:
            return None
        clean_lower = clean.lower()
        # Exact display-name match first
        for u in users:
            if u["display_name"].lower() == clean_lower:
                return u["account_id"]
        # Partial match (all search words appear in display name)
        words = clean_lower.split()
        for u in users:
            dn = u["display_name"].lower()
            if all(w in dn for w in words):
                return u["account_id"]
        # Last resort: first result
        return users[0]["account_id"]
    except Exception as e:
        logger.warning(f"resolve_assignee_account_id failed for '{name}': {e}")
        return None


# ─── LLM ticket generation ───────────────────────────────────────────────────

TICKET_GEN_SYSTEM_PROMPT = """You are a senior engineering manager writing Jira tickets from team intelligence signals.

Given a ThirdEye signal (a structured piece of extracted team knowledge), write a Jira ticket.

Return ONLY a valid JSON object with exactly these fields:
{
  "summary": "Short, actionable ticket title (max 100 chars). Start with a verb. No jargon.",
  "description": "Full ticket description. Include: what the issue is, context from the signal, why it matters, suggested next steps. Use blank lines between sections. Use '- ' for bullet points. Max 400 words.",
  "labels": ["label1", "label2"],
  "assignee_name": "First name or @name of the person to assign, or null if unclear"
}

Label rules:
- Always include "thirdeye" and "auto-raised"
- Add the signal type as a label (e.g. "tech-debt", "recurring-bug")
- Add "urgent" if severity is high or critical
- Labels must not have spaces (use hyphens)

Summary rules:
- Starts with a verb: "Fix", "Investigate", "Address", "Resolve", "Document", "Implement"
- Be specific — "Fix intermittent checkout timeout" NOT "Fix bug"
- Never exceed 100 characters

Description must include:
1. What: clear 1-sentence problem statement
2. Context: what was actually said / detected (cite the signal)
3. Impact: why this matters to the team or product
4. Suggested next steps (2-3 bullet points)

Return JSON only — no markdown, no preamble."""


async def generate_ticket_content(signal: dict) -> dict:
    """
    Use an LLM to generate a clean, context-rich Jira ticket from a ThirdEye signal.
    Returns {"summary": str, "description": str, "labels": list, "assignee_name": str|None}
    """
    signal_text = (
        f"Signal type: {signal.get('type', 'unknown')}\n"
        f"Summary: {signal.get('summary', '')}\n"
        f"Raw quote: {signal.get('raw_quote', '')[:300]}\n"
        f"Severity: {signal.get('severity', 'medium')}\n"
        f"Entities involved: {', '.join(signal.get('entities', []))}\n"
        f"Keywords: {', '.join(signal.get('keywords', []))}\n"
        f"Timestamp: {signal.get('timestamp', '')}\n"
        f"Group: {signal.get('group_id', '')}\n"
        f"Lens: {signal.get('lens', '')}"
    )

    try:
        result = await call_llm(
            task_type="fast_large",
            messages=[
                {"role": "system", "content": TICKET_GEN_SYSTEM_PROMPT},
                {"role": "user", "content": signal_text},
            ],
            temperature=0.2,
            max_tokens=800,
            response_format={"type": "json_object"},
        )
        raw = result["content"].strip()
        if raw.startswith("```"):
            raw = raw.split("```")[1]
            if raw.startswith("json"):
                raw = raw[4:]
        return json.loads(raw)

    except Exception as e:
        logger.warning(f"Ticket generation LLM failed: {e}. Using fallback.")
        # Fallback: build a basic ticket without LLM
        sig_type = signal.get("type", "unknown").replace("_", " ").title()
        return {
            "summary": f"{sig_type}: {signal.get('summary', 'Unknown issue')[:80]}",
            "description": (
                f"Signal detected by ThirdEye.\n\n"
                f"Type: {signal.get('type', 'unknown')}\n"
                f"Summary: {signal.get('summary', '')}\n\n"
                f"Raw context:\n{signal.get('raw_quote', '(none)')[:300]}\n\n"
                f"Severity: {signal.get('severity', 'medium')}"
            ),
            "labels": ["thirdeye", "auto-raised", signal.get("type", "unknown").replace("_", "-")],
            "assignee_name": None,
        }


# ─── Main raise function ──────────────────────────────────────────────────────

async def raise_ticket_for_signal(
    signal: dict,
    group_id: str,
    project_key: str = None,
    force: bool = False,
    assignee_account_id: str = None,
) -> dict:
    """
    Create a Jira ticket for a single ThirdEye signal.

    Args:
        signal:      The signal dict from ChromaDB
        group_id:    The group this signal belongs to (for dedup tracking)
        project_key: Override project (default: JIRA_DEFAULT_PROJECT)
        force:       If True, raise even if already raised before

    Returns:
        {"ok": True, "key": "ENG-42", "url": "...", "summary": "..."}
        OR
        {"ok": False, "reason": "already_raised" | "not_raiseable" | "jira_error", ...}
    """
    if not is_configured():
        return {"ok": False, "reason": "jira_not_configured"}

    signal_id = signal.get("id", "")
    signal_type = signal.get("type", "")

    # Check if this signal type is raiseable
    if signal_type not in RAISEABLE_TYPES:
        return {"ok": False, "reason": "not_raiseable", "signal_type": signal_type}

    # Check if already raised (skip if force=True)
    if not force and signal_id:
        already_raised = get_raised_signal_ids(group_id)
        if signal_id in already_raised:
            return {"ok": False, "reason": "already_raised", "signal_id": signal_id}

    # Determine Jira issue type and priority from signal
    default_type, default_priority = SIGNAL_TYPE_MAP.get(signal_type, (JIRA_DEFAULT_ISSUE_TYPE, "Medium"))
    severity = signal.get("severity", "medium").lower()
    priority = SEVERITY_TO_PRIORITY.get(severity, default_priority)

    # Generate ticket content via LLM
    ticket_content = await generate_ticket_content(signal)

    summary = ticket_content.get("summary", signal.get("summary", "ThirdEye signal")[:100])
    description = ticket_content.get("description", signal.get("summary", ""))
    labels = ticket_content.get("labels", ["thirdeye", "auto-raised"])
    # Always ensure thirdeye label is present
    if "thirdeye" not in labels:
        labels.append("thirdeye")

    # Append ThirdEye metadata as a context section in the description
    meta_section = (
        f"\n\n---\n"
        f"Raised by: ThirdEye\n"
        f"Signal ID: {signal_id}\n"
        f"Group: {group_id}\n"
        f"Detected: {signal.get('timestamp', datetime.utcnow().isoformat())}"
    )
    description = description + meta_section

    # Resolve assignee: explicit account_id wins, then signal override name, then LLM-extracted name
    if not assignee_account_id:
        name_hint = signal.get("assignee_override") or ticket_content.get("assignee_name")
        if name_hint:
            assignee_account_id = await resolve_assignee_account_id(name_hint)
            if assignee_account_id:
                logger.info(f"Resolved assignee '{name_hint}' → {assignee_account_id}")
            else:
                logger.warning(f"Could not resolve assignee '{name_hint}' to a Jira account")

    # Create the ticket
    result = await create_issue(
        project_key=project_key or JIRA_DEFAULT_PROJECT,
        summary=summary,
        description=description,
        issue_type=default_type,
        priority=priority,
        labels=labels,
        assignee_account_id=assignee_account_id,
    )

    if result.get("ok"):
        jira_key = result["key"]
        jira_url = result["url"]
        # Mark this signal as raised in ChromaDB so we never duplicate it
        if signal_id:
            mark_signal_as_raised(
                group_id, signal_id, jira_key,
                jira_url=jira_url,
                jira_summary=summary,
                jira_priority=priority,
            )
        logger.info(f"Raised Jira ticket {jira_key} for signal {signal_id} ({signal_type})")
        return {
            "ok": True,
            "key": jira_key,
            "url": jira_url,
            "summary": summary,
            "issue_type": default_type,
            "priority": priority,
            "assignee_account_id": assignee_account_id,
        }
    else:
        logger.error(f"Jira ticket creation failed: {result}")
        return {
            "ok": False,
            "reason": "jira_error",
            "error": result.get("error"),
            "details": result.get("details"),
        }


async def bulk_raise_for_group(
    group_id: str,
    signals: list[dict],
    min_severity: str = None,
    project_key: str = None,
    max_tickets: int = 10,
) -> list[dict]:
    """
    Raise Jira tickets for multiple signals from a group in one call.

    Filters:
    - Only raiseable signal types
    - Only signals at or above min_severity (defaults to JIRA_AUTO_RAISE_SEVERITY)
    - Skips signals already raised
    - Caps at max_tickets to avoid flooding Jira

    Returns list of raise results.
    """
    min_sev = (min_severity or JIRA_AUTO_RAISE_SEVERITY).lower()
    severity_rank = {"low": 0, "medium": 1, "high": 2, "critical": 3}
    min_rank = severity_rank.get(min_sev, 2)  # Default: high

    already_raised = get_raised_signal_ids(group_id)
    candidates = []

    for sig in signals:
        sig_type = sig.get("type", "")
        sig_id = sig.get("id", "")
        severity = sig.get("severity", "low").lower()
        rank = severity_rank.get(severity, 0)

        if sig_type not in RAISEABLE_TYPES:
            continue
        if rank < min_rank:
            continue
        if sig_id in already_raised:
            continue
        candidates.append(sig)

    # Sort by severity descending, then raise up to max_tickets
    candidates.sort(key=lambda s: severity_rank.get(s.get("severity", "low"), 0), reverse=True)
    candidates = candidates[:max_tickets]

    results = []
    for sig in candidates:
        result = await raise_ticket_for_signal(sig, group_id, project_key=project_key)
        results.append({**result, "signal_type": sig.get("type"), "signal_summary": sig.get("summary", "")[:80]})

    logger.info(f"Bulk raise for group {group_id}: {len(results)} tickets from {len(signals)} signals")
    return results


def format_raise_result_for_telegram(result: dict) -> str:
    """Format a single raise result as a Telegram message line."""
    if result.get("ok"):
        return (
            f"✅ [{result['key']}]({result['url']}) — "
            f"*{result.get('issue_type', 'Task')}* | {result.get('priority', 'Medium')} priority\n"
            f"   _{result.get('summary', '')[:90]}_"
        )
    reason = result.get("reason", "unknown")
    if reason == "already_raised":
        return f"⏭️ Already raised — skipped"
    if reason == "not_raiseable":
        return f"⚪ Signal type `{result.get('signal_type', '?')}` — not mapped to Jira"
    return f"❌ Failed: {result.get('error', reason)}"