from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, Iterable, List, Optional, Sequence
from uuid import UUID

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

from ..models import (
    InsightTicketSeverity,
    InsightTicketStatus,
    InsightTicketType,
    TelemetryInsightAction,
    TelemetryInsightSample,
    TelemetryInsightTicket,
    utcnow,
)


_SEVERITY_ORDER = {
    InsightTicketSeverity.LOW: 0,
    InsightTicketSeverity.MEDIUM: 1,
    InsightTicketSeverity.HIGH: 2,
    InsightTicketSeverity.CRITICAL: 3,
}


class TelemetryInsightRepo:
    """Persistence helpers for telemetry insight tickets."""

    def __init__(self, session: AsyncSession):
        self.session = session

    async def get_ticket(
        self, ticket_id: str | UUID | TelemetryInsightTicket
    ) -> Optional[TelemetryInsightTicket]:
        if isinstance(ticket_id, TelemetryInsightTicket):
            return ticket_id
        return await self.session.get(TelemetryInsightTicket, ticket_id)

    async def get_by_fingerprint(self, fingerprint: str) -> Optional[TelemetryInsightTicket]:
        stmt = (
            select(TelemetryInsightTicket)
            .where(TelemetryInsightTicket.fingerprint == fingerprint)
            .limit(1)
        )
        return (await self.session.execute(stmt)).scalars().first()

    async def upsert_ticket(
        self,
        *,
        fingerprint: str,
        ticket_type: InsightTicketType,
        severity: InsightTicketSeverity,
        title: str,
        status: InsightTicketStatus = InsightTicketStatus.OPEN,
        tldr: Optional[str] = None,
        context_json: Optional[Dict[str, Any]] = None,
        primary_org_id: Optional[str] = None,
        affected_org_ids: Optional[Sequence[str]] = None,
        feature: Optional[str] = None,
        action: Optional[str] = None,
        error_code: Optional[str] = None,
        source_version: Optional[str] = None,
        event_count: int = 1,
        affected_orgs: int = 1,
        affected_paying_orgs: int = 0,
        first_seen_at: Optional[datetime] = None,
        last_seen_at: Optional[datetime] = None,
    ) -> TelemetryInsightTicket:
        existing = await self.get_by_fingerprint(fingerprint)
        now = utcnow()
        first_seen_at = first_seen_at or now
        last_seen_at = last_seen_at or first_seen_at

        if existing:
            existing.title = title or existing.title
            if tldr is not None:
                existing.tldr = tldr
            if context_json is not None:
                existing.context_json = context_json

            if feature is not None:
                existing.feature = feature
            if action is not None:
                existing.action = action
            if error_code is not None:
                existing.error_code = error_code
            if source_version is not None:
                existing.source_version = source_version
            if primary_org_id is not None:
                existing.primary_org_id = primary_org_id

            if affected_org_ids:
                existing_ids = set(existing.affected_org_ids or [])
                merged = sorted(existing_ids | set(affected_org_ids))
                existing.affected_org_ids = merged

            existing.event_count = max(existing.event_count or 0, event_count or 0)
            existing.affected_orgs = max(existing.affected_orgs or 0, affected_orgs or 0)
            existing.affected_paying_orgs = max(
                existing.affected_paying_orgs or 0, affected_paying_orgs or 0
            )
            if existing.first_seen_at:
                existing.first_seen_at = min(existing.first_seen_at, first_seen_at)
            else:
                existing.first_seen_at = first_seen_at
            if existing.last_seen_at:
                existing.last_seen_at = max(existing.last_seen_at, last_seen_at)
            else:
                existing.last_seen_at = last_seen_at

            if severity is not None:
                if existing.severity is None:
                    existing.severity = severity
                else:
                    current_rank = _SEVERITY_ORDER.get(existing.severity, 0)
                    incoming_rank = _SEVERITY_ORDER.get(severity, 0)
                    if incoming_rank > current_rank:
                        existing.severity = severity

            if existing.status == InsightTicketStatus.OPEN:
                existing.status = status

            existing.updated_at = now
            self.session.add(existing)
            await self.session.commit()
            await self.session.refresh(existing)
            return existing

        ticket = TelemetryInsightTicket(
            fingerprint=fingerprint,
            type=ticket_type,
            severity=severity,
            status=status,
            title=title,
            tldr=tldr,
            context_json=context_json or {},
            primary_org_id=primary_org_id,
            affected_org_ids=list(affected_org_ids) if affected_org_ids else None,
            feature=feature,
            action=action,
            error_code=error_code,
            source_version=source_version,
            event_count=event_count,
            affected_orgs=affected_orgs,
            affected_paying_orgs=affected_paying_orgs,
            first_seen_at=first_seen_at,
            last_seen_at=last_seen_at,
            created_at=now,
            updated_at=now,
        )
        self.session.add(ticket)
        await self.session.commit()
        await self.session.refresh(ticket)
        return ticket

    async def add_action(
        self,
        *,
        ticket_id: str | UUID,
        action_type: str,
        actor_email: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
    ) -> TelemetryInsightAction:
        action = TelemetryInsightAction(
            ticket_id=ticket_id,
            action_type=action_type,
            actor_email=actor_email,
            details=details,
            created_at=utcnow(),
        )
        self.session.add(action)
        await self.session.commit()
        await self.session.refresh(action)
        return action

    async def add_samples(
        self, *, ticket_id: str | UUID, saas_event_ids: Iterable[str | UUID]
    ) -> int:
        event_ids = [event_id for event_id in saas_event_ids if event_id]
        if not event_ids:
            return 0

        samples = [
            TelemetryInsightSample(ticket_id=ticket_id, saas_event_id=event_id)
            for event_id in event_ids
        ]
        self.session.add_all(samples)
        try:
            await self.session.commit()
            return len(samples)
        except IntegrityError:
            await self.session.rollback()

        inserted = 0
        for event_id in event_ids:
            exists_stmt = (
                select(TelemetryInsightSample.id)
                .where(TelemetryInsightSample.ticket_id == ticket_id)
                .where(TelemetryInsightSample.saas_event_id == event_id)
                .limit(1)
            )
            exists = (await self.session.execute(exists_stmt)).scalar()
            if exists:
                continue
            self.session.add(
                TelemetryInsightSample(ticket_id=ticket_id, saas_event_id=event_id)
            )
            inserted += 1

        if inserted:
            await self.session.commit()
        return inserted

    async def list_tickets(
        self,
        *,
        status: Optional[InsightTicketStatus] = None,
        severity: Optional[InsightTicketSeverity] = None,
        ticket_type: Optional[InsightTicketType] = None,
        feature: Optional[str] = None,
        limit: int = 100,
    ) -> List[TelemetryInsightTicket]:
        stmt = select(TelemetryInsightTicket)
        if status:
            stmt = stmt.where(TelemetryInsightTicket.status == status)
        if severity:
            stmt = stmt.where(TelemetryInsightTicket.severity == severity)
        if ticket_type:
            stmt = stmt.where(TelemetryInsightTicket.type == ticket_type)
        if feature:
            stmt = stmt.where(TelemetryInsightTicket.feature == feature)
        stmt = stmt.order_by(TelemetryInsightTicket.last_seen_at.desc()).limit(limit)
        return list((await self.session.execute(stmt)).scalars().all())
