"""Reindex trigger and request management."""
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any
from uuid import uuid4

from .converters import row_to_reindex_request, row_to_reindex_trigger
from .models import ReindexRequest, ReindexTrigger, utcnow

if TYPE_CHECKING:
    from .engine import RetrievalLearningEngine


class ReindexManagerMixin:
    """Mixin for reindex management functionality."""

    @staticmethod
    def list_reindex_triggers(
        engine: "RetrievalLearningEngine",
        *,
        workspace_id: str | None = None,
    ) -> list[ReindexTrigger]:
        """List reindex triggers."""
        workspace_key = engine._workspace_value(workspace_id)
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            rows = connection.execute(
                f"""
                SELECT * FROM {engine._table('reindex_triggers')}
                WHERE workspace_id = ?
                ORDER BY updated_at DESC
                """,
                (workspace_key,),
            ).fetchall()
        return [row_to_reindex_trigger(row, workspace_id) for row in rows]

    @staticmethod
    def get_reindex_trigger(
        engine: "RetrievalLearningEngine",
        trigger_id: str,
    ) -> ReindexTrigger | None:
        """Get reindex trigger by ID."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_triggers')} WHERE id = ?",
                (trigger_id,),
            ).fetchone()
        return row_to_reindex_trigger(
            row,
            None if row is None or row["workspace_id"] == "__global__" else row["workspace_id"],
        ) if row is not None else None

    @staticmethod
    def _update_reindex_trigger_status(
        engine: "RetrievalLearningEngine",
        trigger_id: str,
        *,
        status: str,
        note: str | None = None,
    ) -> ReindexTrigger | None:
        """Update reindex trigger status."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_triggers')} WHERE id = ?",
                (trigger_id,),
            ).fetchone()
            if row is None:
                return None
            metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
            if note:
                metadata["operator_note"] = note
            updated_at = utcnow()
            connection.execute(
                f"""
                UPDATE {engine._table('reindex_triggers')}
                SET status = ?, metadata_json = ?, updated_at = ?
                WHERE id = ?
                """,
                (status, json.dumps(metadata, sort_keys=True), updated_at, trigger_id),
            )
            connection.commit()
            updated_row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_triggers')} WHERE id = ?",
                (trigger_id,),
            ).fetchone()
        return row_to_reindex_trigger(
            updated_row,
            None if updated_row is None or updated_row["workspace_id"] == "__global__" else updated_row["workspace_id"],
        ) if updated_row is not None else None

    @staticmethod
    def acknowledge_reindex_trigger(
        engine: "RetrievalLearningEngine",
        trigger_id: str,
        *,
        note: str | None = None,
    ) -> ReindexTrigger | None:
        """Acknowledge a reindex trigger."""
        return ReindexManagerMixin._update_reindex_trigger_status(engine, trigger_id, status="acknowledged", note=note)

    @staticmethod
    def resolve_reindex_trigger(
        engine: "RetrievalLearningEngine",
        trigger_id: str,
        *,
        note: str | None = None,
    ) -> ReindexTrigger | None:
        """Resolve a reindex trigger."""
        return ReindexManagerMixin._update_reindex_trigger_status(engine, trigger_id, status="resolved", note=note)

    @staticmethod
    def close_reindex_trigger(
        engine: "RetrievalLearningEngine",
        trigger_id: str,
        *,
        note: str | None = None,
    ) -> ReindexTrigger | None:
        """Close a reindex trigger."""
        return ReindexManagerMixin._update_reindex_trigger_status(engine, trigger_id, status="closed", note=note)

    @staticmethod
    def create_force_reindex_request(
        engine: "RetrievalLearningEngine",
        *,
        workspace_id: str | None,
        source_key: str,
        reason: str = "operator_requested",
        requested_by: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> ReindexRequest:
        """Create a force reindex request."""
        workspace_key = engine._workspace_value(workspace_id)
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            candidates = connection.execute(
                f"""
                SELECT * FROM {engine._table('reindex_requests')}
                WHERE workspace_id = ? AND source_key = ? AND status IN ('queued', 'running')
                ORDER BY updated_at DESC
                """,
                (workspace_key, source_key),
            ).fetchall()
            existing = next((row for row in candidates if engine._is_request_coalescible(row)), None)
            if existing is not None:
                payload = json.loads(existing["metadata_json"]) if existing["metadata_json"] else {}
                payload["coalesced_count"] = int(payload.get("coalesced_count", 0)) + 1
                payload.setdefault("coalesced_reasons", [])
                if reason not in payload["coalesced_reasons"]:
                    payload["coalesced_reasons"].append(reason)
                if requested_by:
                    payload.setdefault("coalesced_requesters", [])
                    if requested_by not in payload["coalesced_requesters"]:
                        payload["coalesced_requesters"].append(requested_by)
                payload["last_coalesced_at"] = utcnow()
                payload["dedupe_window_seconds"] = engine.dedupe_window_seconds
                if metadata:
                    payload.update(metadata)
                updated_at = utcnow()
                connection.execute(
                    f"""
                    UPDATE {engine._table('reindex_requests')}
                    SET metadata_json = ?, updated_at = ?
                    WHERE id = ?
                    """,
                    (json.dumps(payload, sort_keys=True), updated_at, existing["id"]),
                )
                connection.commit()
                refreshed = connection.execute(
                    f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                    (existing["id"],),
                ).fetchone()
                return row_to_reindex_request(
                    refreshed,
                    None if refreshed is None or refreshed["workspace_id"] == "__global__" else refreshed["workspace_id"],
                )

        request = ReindexRequest(
            id=uuid4().hex,
            workspace_id=workspace_id,
            source_key=source_key,
            reason=reason,
            requested_by=requested_by,
            metadata=metadata or {},
        )
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            connection.execute(
                f"""
                INSERT INTO {engine._table('reindex_requests')}
                (id, workspace_id, source_key, reason, requested_by, status, metadata_json, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    request.id,
                    workspace_key,
                    source_key,
                    reason,
                    requested_by,
                    request.status,
                    json.dumps(request.metadata, sort_keys=True),
                    request.created_at,
                    request.updated_at,
                ),
            )
            connection.commit()
        return request

    @staticmethod
    def find_open_reindex_request(
        engine: "RetrievalLearningEngine",
        *,
        workspace_id: str | None,
        source_key: str,
    ) -> ReindexRequest | None:
        """Find open reindex request for source."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            rows = connection.execute(
                f"""
                SELECT * FROM {engine._table('reindex_requests')}
                WHERE workspace_id = ? AND source_key = ? AND status IN ('queued', 'running')
                ORDER BY updated_at DESC
                """,
                (engine._workspace_value(workspace_id), source_key),
            ).fetchall()
            row = next((candidate for candidate in rows if engine._is_request_coalescible(candidate)), None)
        return row_to_reindex_request(
            row,
            None if row is None or row["workspace_id"] == "__global__" else row["workspace_id"],
        ) if row is not None else None

    @staticmethod
    def list_reindex_requests(
        engine: "RetrievalLearningEngine",
        *,
        workspace_id: str | None = None,
    ) -> list[ReindexRequest]:
        """List reindex requests."""
        workspace_key = engine._workspace_value(workspace_id)
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            rows = connection.execute(
                f"""
                SELECT * FROM {engine._table('reindex_requests')}
                WHERE workspace_id = ?
                ORDER BY updated_at DESC
                """,
                (workspace_key,),
            ).fetchall()
        return [row_to_reindex_request(row, workspace_id) for row in rows]

    @staticmethod
    def get_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
    ) -> ReindexRequest | None:
        """Get reindex request by ID."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
        return row_to_reindex_request(
            row,
            None if row is None or row["workspace_id"] == "__global__" else row["workspace_id"],
        ) if row is not None else None

    @staticmethod
    def _update_reindex_request_status(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        status: str,
        note: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> ReindexRequest | None:
        """Update reindex request status."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
            if row is None:
                return None
            payload = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
            if metadata:
                payload.update(metadata)
            if note:
                payload["operator_note"] = note
            updated_at = utcnow()
            connection.execute(
                f"""
                UPDATE {engine._table('reindex_requests')}
                SET status = ?, metadata_json = ?, updated_at = ?
                WHERE id = ?
                """,
                (status, json.dumps(payload, sort_keys=True), updated_at, request_id),
            )
            connection.commit()
            updated_row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
        return row_to_reindex_request(
            updated_row,
            None if updated_row is None or updated_row["workspace_id"] == "__global__" else updated_row["workspace_id"],
        ) if updated_row is not None else None

    @staticmethod
    def start_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
    ) -> ReindexRequest | None:
        """Start a reindex request."""
        return ReindexManagerMixin._update_reindex_request_status(engine, request_id, status="running", note=note)

    @staticmethod
    def complete_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> ReindexRequest | None:
        """Complete a reindex request."""
        return ReindexManagerMixin._update_reindex_request_status(engine, request_id, status="completed", note=note, metadata=metadata)

    @staticmethod
    def fail_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> ReindexRequest | None:
        """Fail a reindex request."""
        return ReindexManagerMixin._update_reindex_request_status(engine, request_id, status="failed", note=note, metadata=metadata)

    @staticmethod
    def retry_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
    ) -> ReindexRequest | None:
        """Retry a failed reindex request."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
            if row is None or row["status"] != "failed":
                return None
            metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
            metadata["retry_count"] = int(metadata.get("retry_count", 0)) + 1
            if note:
                metadata["operator_note"] = note
            metadata.pop("error", None)
            metadata.pop("duration_ms", None)
            updated_at = utcnow()
            connection.execute(
                f"""
                UPDATE {engine._table('reindex_requests')}
                SET status = ?, metadata_json = ?, updated_at = ?
                WHERE id = ?
                """,
                ("queued", json.dumps(metadata, sort_keys=True), updated_at, request_id),
            )
            connection.commit()
            updated_row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
        return row_to_reindex_request(
            updated_row,
            None if updated_row is None or updated_row["workspace_id"] == "__global__" else updated_row["workspace_id"],
        ) if updated_row is not None else None

    @staticmethod
    def resume_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
    ) -> ReindexRequest | None:
        """Resume a canceled reindex request."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
            if row is None or row["status"] != "canceled":
                return None
            metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
            metadata["resume_count"] = int(metadata.get("resume_count", 0)) + 1
            if note:
                metadata["operator_note"] = note
            updated_at = utcnow()
            connection.execute(
                f"""
                UPDATE {engine._table('reindex_requests')}
                SET status = ?, metadata_json = ?, updated_at = ?
                WHERE id = ?
                """,
                ("queued", json.dumps(metadata, sort_keys=True), updated_at, request_id),
            )
            connection.commit()
            updated_row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
        return row_to_reindex_request(
            updated_row,
            None if updated_row is None or updated_row["workspace_id"] == "__global__" else updated_row["workspace_id"],
        ) if updated_row is not None else None

    @staticmethod
    def replay_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
    ) -> ReindexRequest | None:
        """Replay a reindex request."""
        original = ReindexManagerMixin.get_reindex_request(engine, request_id)
        if original is None or original.status != "canceled":
            return None
        metadata = dict(original.metadata)
        metadata["replay_of"] = original.id
        metadata["replay_count"] = int(metadata.get("replay_count", 0)) + 1
        if note:
            metadata["operator_note"] = note
        return ReindexManagerMixin.create_force_reindex_request(
            engine,
            workspace_id=original.workspace_id,
            source_key=original.source_key,
            reason=original.reason,
            requested_by=original.requested_by,
            metadata=metadata,
        )

    @staticmethod
    def cancel_reindex_request(
        engine: "RetrievalLearningEngine",
        request_id: str,
        *,
        note: str | None = None,
    ) -> ReindexRequest | None:
        """Cancel a reindex request."""
        with engine._connect() as connection:
            engine._ensure_schema(connection)
            row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
            if row is None or row["status"] not in {"queued", "running"}:
                return None
            metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else {}
            if note:
                metadata["operator_note"] = note
            updated_at = utcnow()
            connection.execute(
                f"""
                UPDATE {engine._table('reindex_requests')}
                SET status = ?, metadata_json = ?, updated_at = ?
                WHERE id = ?
                """,
                ("canceled", json.dumps(metadata, sort_keys=True), updated_at, request_id),
            )
            connection.commit()
            updated_row = connection.execute(
                f"SELECT * FROM {engine._table('reindex_requests')} WHERE id = ?",
                (request_id,),
            ).fetchone()
        return row_to_reindex_request(
            updated_row,
            None if updated_row is None or updated_row["workspace_id"] == "__global__" else updated_row["workspace_id"],
        ) if updated_row is not None else None
