from __future__ import annotations

import hashlib
import json
import logging
import os
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from ..models import TelemetryInsightAction
from ..providers.vertex_ai_provider import VertexAIProvider

INSIGHT_TRIAGE_SCHEMA: Dict[str, Any] = {
    "type": "object",
    "properties": {
        "tldr": {"type": "string"},
        "root_cause_hypotheses": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "cause": {"type": "string"},
                    "confidence": {"type": "number"},
                    "evidence": {"type": "array", "items": {"type": "string"}},
                },
            },
        },
        "suggested_actions": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "action": {"type": "string"},
                    "priority": {"type": "string", "enum": ["high", "medium", "low"]},
                },
            },
        },
        "github_issue_draft": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "body": {"type": "string"},
                "labels": {"type": "array", "items": {"type": "string"}},
            },
        },
        "affected_users_action": {"type": "string"},
    },
}

INSIGHT_TRIAGE_PROMPT = """You are a senior SRE triaging a telemetry insight ticket for FoundryMatch, a B2B data matching SaaS.

## Insight Ticket
- Type: {ticket_type}
- Title: {title}
- Severity: {severity}
- First Seen: {first_seen}
- Last Seen: {last_seen}

## Dimensions
- Feature: {feature}
- Action: {action}
- Error Code: {error_code}
- Source Version: {source_version}

## Stats
- Event Count: {event_count}
- Affected Orgs: {affected_orgs}
- Paying Orgs: {affected_paying_orgs}

## Context
{context_json}

## Sample Events
{sample_events}

## RULES
1. CITE EVIDENCE: Reference specific context fields or sample event patterns
2. CONFIDENCE: Be honest. Low confidence if data is sparse.
3. ACTIONABLE: Suggest concrete next steps (check logs, rollback, hotfix, etc.)
4. GITHUB READY: Issue draft should be copy-paste ready with reproduction steps

## Task
Analyze and return JSON with: tldr, root_cause_hypotheses, suggested_actions, github_issue_draft, affected_users_action"""


def compute_triage_cache_key(ticket: Any) -> str:
    return hashlib.md5(
        f"{ticket.id}|{ticket.last_seen_at.isoformat()}|{ticket.event_count}".encode()
    ).hexdigest()


class InsightTriageService:
    def __init__(self, model: Optional[str] = None, cache_minutes: Optional[int] = None):
        resolved_model = model or os.getenv(
            "INSIGHT_TRIAGE_MODEL", "gemini-3-flash-preview"
        )
        self.provider = VertexAIProvider(model=resolved_model)
        self.cache_minutes = int(
            cache_minutes or os.getenv("INSIGHT_TRIAGE_CACHE_MINUTES", "30")
        )
        self.context_limit = int(
            os.getenv("INSIGHT_TRIAGE_CONTEXT_MAX_CHARS", "4000")
        )
        self.max_output_tokens = int(
            os.getenv("INSIGHT_TRIAGE_MAX_OUTPUT_TOKENS", "2048")
        )
        self.timeout_s = int(os.getenv("INSIGHT_TRIAGE_TIMEOUT_S", "60"))
        self._log = logging.getLogger(__name__)

    async def run_triage(
        self,
        *,
        db: AsyncSession,
        ticket: Any,
        sample_events: List[Any],
        force: bool = False,
    ) -> Tuple[Dict[str, Any], str, bool]:
        context_hash = compute_triage_cache_key(ticket)
        if not force and ticket.ai_triage_hash == context_hash:
            cached = await self._get_cached_triage(db, ticket.id, context_hash)
            if cached:
                if ticket.ai_triage_last_run_at:
                    last_run = ticket.ai_triage_last_run_at
                    if last_run.tzinfo is None:
                        last_run = last_run.replace(tzinfo=timezone.utc)
                    age_minutes = (
                        datetime.now(timezone.utc) - last_run
                    ).total_seconds() / 60
                    if age_minutes < self.cache_minutes:
                        return cached, context_hash, True

        context = self._build_context(ticket, sample_events)
        prompt = self._build_prompt(context)
        result = self.provider.generate_json(
            prompt=prompt,
            schema=INSIGHT_TRIAGE_SCHEMA,
            thinking_level="MEDIUM",
            max_output_tokens=self.max_output_tokens,
            timeout_s=self.timeout_s,
        )
        value, raw_text = self._parse_result(result)
        if value is None:
            value = self._fallback_value(raw_text)
        return value, context_hash, False

    async def _get_cached_triage(
        self, db: AsyncSession, ticket_id: Any, context_hash: str
    ) -> Optional[Dict[str, Any]]:
        stmt = (
            select(TelemetryInsightAction)
            .where(
                TelemetryInsightAction.ticket_id == ticket_id,
                TelemetryInsightAction.action_type == "ai_triage",
            )
            .order_by(TelemetryInsightAction.created_at.desc())
            .limit(5)
        )
        rows = (await db.execute(stmt)).scalars().all()
        for row in rows:
            details = row.details or {}
            if details.get("context_hash") == context_hash:
                cached = details.get("result")
                if isinstance(cached, dict):
                    return cached
        return None

    def _build_context(self, ticket: Any, sample_events: List[Any]) -> Dict[str, Any]:
        samples = [
            {
                "action": event.action,
                "status": event.status.value if getattr(event, "status", None) else None,
                "duration_ms": event.duration_ms,
                "source_version": event.source_version,
                "ts": event.ts.isoformat() if event.ts else None,
            }
            for event in sample_events
        ]
        return {
            "ticket_id": str(ticket.id),
            "type": ticket.type.value if ticket.type else None,
            "severity": ticket.severity.value if ticket.severity else None,
            "status": ticket.status.value if ticket.status else None,
            "title": ticket.title,
            "feature": ticket.feature,
            "action": ticket.action,
            "error_code": ticket.error_code,
            "source_version": ticket.source_version,
            "event_count": ticket.event_count,
            "affected_orgs": ticket.affected_orgs,
            "affected_paying_orgs": ticket.affected_paying_orgs,
            "first_seen": ticket.first_seen_at.isoformat()
            if ticket.first_seen_at
            else None,
            "last_seen": ticket.last_seen_at.isoformat() if ticket.last_seen_at else None,
            "context_json": ticket.context_json or {},
            "sample_events": samples,
        }

    def _build_prompt(self, context: Dict[str, Any]) -> str:
        context_json = json.dumps(context.get("context_json", {}), indent=2)
        sample_events = json.dumps(context.get("sample_events", []), indent=2)
        if len(context_json) > self.context_limit:
            context_json = context_json[: self.context_limit] + "...(truncated)"
        if len(sample_events) > self.context_limit:
            sample_events = sample_events[: self.context_limit] + "...(truncated)"
        return INSIGHT_TRIAGE_PROMPT.format(
            ticket_type=context.get("type") or "",
            title=context.get("title") or "",
            severity=context.get("severity") or "",
            first_seen=context.get("first_seen") or "",
            last_seen=context.get("last_seen") or "",
            feature=context.get("feature") or "",
            action=context.get("action") or "",
            error_code=context.get("error_code") or "",
            source_version=context.get("source_version") or "",
            event_count=context.get("event_count") or 0,
            affected_orgs=context.get("affected_orgs") or 0,
            affected_paying_orgs=context.get("affected_paying_orgs") or 0,
            context_json=context_json,
            sample_events=sample_events,
        )

    def _parse_result(
        self, result: Any
    ) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
        if not isinstance(result, dict):
            return None, None

        value = result.get("value")
        if isinstance(value, dict):
            return self._normalize_value(value), result.get("raw_text")

        raw_text = result.get("raw_text")
        if isinstance(raw_text, str):
            try:
                parsed = json.loads(raw_text)
            except Exception:
                parsed = None
            if isinstance(parsed, dict):
                return self._normalize_value(parsed), raw_text
            return None, raw_text

        return None, None

    def _normalize_value(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        return {
            "tldr": payload.get("tldr") or "",
            "root_cause_hypotheses": payload.get("root_cause_hypotheses") or [],
            "suggested_actions": payload.get("suggested_actions") or [],
            "github_issue_draft": payload.get("github_issue_draft") or {},
            "affected_users_action": payload.get("affected_users_action") or "",
        }

    def _fallback_value(self, raw_text: Optional[str]) -> Dict[str, Any]:
        tldr = "AI triage could not parse a response. Please retry."
        if raw_text:
            self._log.warning("Insight triage parse failed. raw_text=%s", raw_text[:200])
        return {
            "tldr": tldr,
            "root_cause_hypotheses": [],
            "suggested_actions": [],
            "github_issue_draft": {},
            "affected_users_action": "",
        }
