"""Orchestrator event log + email log + per-pool prune helpers."""
from __future__ import annotations

import json
from datetime import datetime
from typing import Any, Optional

from sqlalchemy import delete as sa_delete
from sqlalchemy import desc, func, or_, select
from sqlmodel import col

from decnet.web.db.models import OrchestratorEmail, OrchestratorEvent


from decnet.web.db.sqlmodel_repo._helpers import _MixinBase

class OrchestratorMixin(_MixinBase):
    """Mixin: composed onto ``SQLModelRepository``."""

    async def record_orchestrator_event(self, data: dict[str, Any]) -> str:
        payload = data.get("payload")
        if isinstance(payload, (dict, list)):
            data = {**data, "payload": json.dumps(payload)}
        async with self._session() as session:
            row = OrchestratorEvent(**data)
            session.add(row)
            await session.commit()
            await session.refresh(row)
            return row.uuid

    async def list_orchestrator_events(
        self,
        limit: int = 100,
        offset: int = 0,
        *,
        kind: Optional[str] = None,
        since_ts: Optional[datetime] = None,
    ) -> list[dict[str, Any]]:
        async with self._session() as session:
            stmt = select(OrchestratorEvent)
            if kind is not None:
                stmt = stmt.where(OrchestratorEvent.kind == kind)
            if since_ts is not None:
                stmt = stmt.where(OrchestratorEvent.ts >= since_ts)
            stmt = (
                stmt.order_by(desc(OrchestratorEvent.ts))
                .offset(offset)
                .limit(limit)
            )
            result = await session.execute(stmt)
            return [r.model_dump(mode="json") for r in result.scalars().all()]

    async def count_orchestrator_events(
        self, *, kind: Optional[str] = None,
    ) -> int:
        stmt = select(func.count()).select_from(OrchestratorEvent)
        if kind is not None:
            stmt = stmt.where(OrchestratorEvent.kind == kind)
        async with self._session() as session:
            result = await session.execute(stmt)
            return result.scalar() or 0

    async def count_orchestrator_failures(
        self,
        *,
        since_ts: datetime,
        kind: Optional[str] = None,
    ) -> int:
        """Count failed orchestrator activity since *since_ts*, across
        both ``orchestrator_events`` (traffic / file) and
        ``orchestrator_emails`` (email).

        Backs the dashboard's failure-count badge — see DEBT-042. The
        in-memory window the badge previously computed against was
        bounded by the SSE-buffer + paginated page, so failures older
        than the local window read low. This is the authoritative count.
        """
        async with self._session() as session:
            ev_stmt = (
                select(func.count()).select_from(OrchestratorEvent)
                .where(
                    col(OrchestratorEvent.success).is_(False),
                    OrchestratorEvent.ts >= since_ts,
                )
            )
            if kind in ("traffic", "file"):
                ev_stmt = ev_stmt.where(OrchestratorEvent.kind == kind)
            em_stmt = (
                select(func.count()).select_from(OrchestratorEmail)
                .where(
                    col(OrchestratorEmail.success).is_(False),
                    OrchestratorEmail.ts >= since_ts,
                )
            )
            ev_count = 0
            em_count = 0
            if kind in (None, "traffic", "file"):
                ev_count = (await session.execute(ev_stmt)).scalar() or 0
            if kind in (None, "email"):
                em_count = (await session.execute(em_stmt)).scalar() or 0
            return ev_count + em_count

    async def prune_orchestrator_events(self, per_dst_cap: int = 10000) -> int:
        """Trim per-dst rows to *per_dst_cap*, oldest-first. Returns deleted count."""
        deleted = 0
        async with self._session() as session:
            dst_rows = await session.execute(
                select(col(OrchestratorEvent.dst_decky_uuid)).distinct()
            )
            for (dst,) in dst_rows.all():
                keep = await session.execute(
                    select(col(OrchestratorEvent.uuid))
                    .where(OrchestratorEvent.dst_decky_uuid == dst)
                    .order_by(desc(OrchestratorEvent.ts))
                    .limit(per_dst_cap)
                )
                keep_uuids = [u for (u,) in keep.all()]
                if not keep_uuids:
                    continue
                stmt = sa_delete(OrchestratorEvent).where(
                    OrchestratorEvent.dst_decky_uuid == dst,
                    col(OrchestratorEvent.uuid).notin_(keep_uuids),
                )
                res = await session.execute(stmt)
                deleted += res.rowcount or 0
            await session.commit()
        return deleted

    async def record_orchestrator_email(self, data: dict[str, Any]) -> str:
        payload = data.get("payload")
        if isinstance(payload, (dict, list)):
            data = {**data, "payload": json.dumps(payload)}
        async with self._session() as session:
            row = OrchestratorEmail(**data)
            session.add(row)
            await session.commit()
            await session.refresh(row)
            return row.uuid

    async def list_orchestrator_emails(
        self,
        limit: int = 100,
        offset: int = 0,
        *,
        mail_decky_uuid: Optional[str] = None,
        thread_id: Optional[str] = None,
        since_ts: Optional[datetime] = None,
    ) -> list[dict[str, Any]]:
        async with self._session() as session:
            stmt = select(OrchestratorEmail)
            if mail_decky_uuid is not None:
                stmt = stmt.where(
                    OrchestratorEmail.mail_decky_uuid == mail_decky_uuid
                )
            if thread_id is not None:
                stmt = stmt.where(OrchestratorEmail.thread_id == thread_id)
            if since_ts is not None:
                stmt = stmt.where(OrchestratorEmail.ts >= since_ts)
            stmt = (
                stmt.order_by(desc(OrchestratorEmail.ts))
                .offset(offset)
                .limit(limit)
            )
            result = await session.execute(stmt)
            return [r.model_dump(mode="json") for r in result.scalars().all()]

    async def count_orchestrator_emails(
        self,
        *,
        mail_decky_uuid: Optional[str] = None,
    ) -> int:
        stmt = select(func.count()).select_from(OrchestratorEmail)
        if mail_decky_uuid is not None:
            stmt = stmt.where(OrchestratorEmail.mail_decky_uuid == mail_decky_uuid)
        async with self._session() as session:
            result = await session.execute(stmt)
            return result.scalar() or 0

    async def list_orchestrator_email_threads(
        self,
        mail_decky_uuid: str,
        sender_email: str,
        recipient_email: str,
        *,
        limit: int = 50,
    ) -> list[dict[str, Any]]:
        # Most-recent row per (sender, recipient) pair under this mail decky.
        # The scheduler only needs the latest message_id/subject/thread_id to
        # construct a reply; older rows in the same thread aren't relevant
        # for the "do we reply or start fresh" decision.
        async with self._session() as session:
            stmt = (
                select(OrchestratorEmail)
                .where(
                    OrchestratorEmail.mail_decky_uuid == mail_decky_uuid,
                    or_(
                        (OrchestratorEmail.sender_email == sender_email)
                        & (OrchestratorEmail.recipient_email == recipient_email),
                        (OrchestratorEmail.sender_email == recipient_email)
                        & (OrchestratorEmail.recipient_email == sender_email),
                    ),
                    col(OrchestratorEmail.success).is_(True),
                )
                .order_by(desc(OrchestratorEmail.ts))
                .limit(limit)
            )
            result = await session.execute(stmt)
            return [r.model_dump(mode="json") for r in result.scalars().all()]

    async def prune_orchestrator_emails(self, per_decky_cap: int = 10000) -> int:
        """Trim per-mail-decky rows to *per_decky_cap*, oldest-first."""
        deleted = 0
        async with self._session() as session:
            decky_rows = await session.execute(
                select(col(OrchestratorEmail.mail_decky_uuid)).distinct()
            )
            for (mail_uuid,) in decky_rows.all():
                keep = await session.execute(
                    select(col(OrchestratorEmail.uuid))
                    .where(OrchestratorEmail.mail_decky_uuid == mail_uuid)
                    .order_by(desc(OrchestratorEmail.ts))
                    .limit(per_decky_cap)
                )
                keep_uuids = [u for (u,) in keep.all()]
                if not keep_uuids:
                    continue
                stmt = sa_delete(OrchestratorEmail).where(
                    OrchestratorEmail.mail_decky_uuid == mail_uuid,
                    col(OrchestratorEmail.uuid).notin_(keep_uuids),
                )
                res = await session.execute(stmt)
                deleted += res.rowcount or 0
            await session.commit()
        return deleted
