"""Rescan service — orchestrates library rescans.

This service handles the actual scan/rescan logic:

- Library rescan via anime_service
- Auto-download of missing episodes (if enabled)
- Folder maintenance scan (if enabled)
- Orphaned folder key resolution

SchedulerService only calls RescanService.execute() — it does not
know about the internal steps.
"""
from __future__ import annotations

import logging
from datetime import datetime, timedelta, timezone
from typing import List, Optional

from src.server.models.config import SchedulerConfig

logger = logging.getLogger(__name__)

_AUTO_DOWNLOAD_COOLDOWN_SECONDS = 300  # 5 minutes


class RescanService:
    """Orchestrates all rescan-related operations.

    Encapsulates the full post-rescan workflow so SchedulerService
    only needs to call a single execute() method.
    """

    def __init__(self, config: Optional[SchedulerConfig] = None) -> None:
        """Initialize the rescan service.

        Args:
            config: Optional scheduler config. If None, operations that depend
                on config flags (auto_download, folder_scan) will be skipped.
        """
        self._config = config
        self._last_scan_time: Optional[datetime] = None
        self._last_auto_download_time: Optional[datetime] = None

    @property
    def last_scan_time(self) -> Optional[datetime]:
        return self._last_scan_time

    # ------------------------------------------------------------------
    # Public entry point
    # ------------------------------------------------------------------

    async def execute(self) -> dict:
        """Execute the full rescan workflow.

        Runs in order:
        1. anime_service.rescan()
        2. auto-download (if enabled)
        3. folder scan (if enabled)
        4. key resolution scan (always, if anime_directory configured)

        Returns:
            Dict with duration and counts for each step.
        """
        from src.server.services.websocket_service import get_websocket_service

        scan_start = datetime.now(timezone.utc)
        results = {
            "started_at": scan_start.isoformat(),
            "duration_seconds": 0.0,
            "rescan_completed": False,
            "auto_download_queued": 0,
            "folder_scan_completed": False,
            "key_resolution": {"resolved": 0, "skipped": 0, "errors": 0},
        }

        await self._broadcast("scheduled_rescan_started", {"timestamp": scan_start.isoformat()})

        try:
            # 1. Main library rescan
            await self._run_rescan()
            results["rescan_completed"] = True

            # 2. Auto-download
            if self._config and self._config.auto_download_after_rescan:
                try:
                    queued = await self._run_auto_download()
                    results["auto_download_queued"] = queued
                    await self._broadcast("auto_download_started", {"queued_count": queued})
                except Exception as exc:
                    logger.error("Auto-download failed: %s", exc, exc_info=True)
                    await self._broadcast("auto_download_error", {"error": str(exc)})

            # 3. Folder scan
            if self._config and self._config.folder_scan_enabled:
                try:
                    await self._run_folder_scan()
                    results["folder_scan_completed"] = True
                except Exception as exc:
                    logger.error("Folder scan failed: %s", exc, exc_info=True)
                    await self._broadcast("folder_scan_error", {"error": str(exc)})

            # 4. Key resolution scan
            try:
                key_stats = await self._run_key_resolution()
                results["key_resolution"] = key_stats
            except Exception as exc:
                logger.error("Key resolution scan failed: %s", exc, exc_info=True)

            self._last_scan_time = datetime.now(timezone.utc)
            results["duration_seconds"] = (self._last_scan_time - scan_start).total_seconds()

            await self._broadcast(
                "scheduled_rescan_completed",
                {
                    "timestamp": self._last_scan_time.isoformat(),
                    "duration_seconds": results["duration_seconds"],
                },
            )

            logger.info(
                "Scheduled library rescan completed: duration=%.2fs",
                results["duration_seconds"],
            )

        except Exception as exc:
            logger.error("Scheduled rescan failed: %s", exc, exc_info=True)
            await self._broadcast(
                "scheduled_rescan_error",
                {"error": str(exc), "timestamp": datetime.now(timezone.utc).isoformat()},
            )
            raise

        return results

    # ------------------------------------------------------------------
    # Step 1: Library rescan
    # ------------------------------------------------------------------

    async def _run_rescan(self) -> None:
        """Run the anime service rescan."""
        from src.server.utils.dependencies import get_anime_service

        anime_service = get_anime_service()
        logger.info("Anime service obtained, calling anime_service.rescan()...")
        await anime_service.rescan()
        logger.info("anime_service.rescan() completed")

    # ------------------------------------------------------------------
    # Step 2: Auto-download
    # ------------------------------------------------------------------

    async def _run_auto_download(self) -> int:
        """Queue and start downloads for all series with missing episodes.

        Returns:
            Number of episodes queued.
        """
        from src.server.models.download import EpisodeIdentifier
        from src.server.utils.dependencies import (
            get_anime_service,
            get_download_service,
        )

        # Cooldown check to prevent rapid re-triggers
        now = datetime.now(timezone.utc)
        if self._last_auto_download_time is not None:
            elapsed = now - self._last_auto_download_time
            if elapsed < timedelta(seconds=_AUTO_DOWNLOAD_COOLDOWN_SECONDS):
                logger.debug(
                    "Auto-download skipped: cooldown active (elapsed=%.1fs cooldown=%ds)",
                    elapsed.total_seconds(),
                    _AUTO_DOWNLOAD_COOLDOWN_SECONDS,
                )
                return 0

        anime_service = get_anime_service()
        download_service = get_download_service()

        series_list = anime_service._cached_list_missing()
        queued_count = 0

        for series in series_list:
            episode_dict: dict = series.get("episodeDict") or {}
            if not episode_dict:
                continue

            episodes: List[EpisodeIdentifier] = []
            for season_str, ep_numbers in episode_dict.items():
                for ep_num in ep_numbers:
                    episodes.append(
                        EpisodeIdentifier(season=int(season_str), episode=int(ep_num))
                    )

            if not episodes:
                continue

            await download_service.add_to_queue(
                serie_id=series.get("key", ""),
                serie_folder=series.get("folder", series.get("name", "")),
                serie_name=series.get("name", ""),
                episodes=episodes,
            )
            queued_count += len(episodes)
            logger.info(
                "Auto-download queued episodes for series=%s count=%d",
                series.get("key"),
                len(episodes),
            )

        if queued_count:
            await download_service.start_queue_processing()
            logger.info("Auto-download queue processing started: queued=%d", queued_count)

        self._last_auto_download_time = datetime.now(timezone.utc)
        logger.info("Auto-download completed: queued_count=%d", queued_count)
        return queued_count

    # ------------------------------------------------------------------
    # Step 3: Folder scan
    # ------------------------------------------------------------------

    async def _run_folder_scan(self) -> None:
        """Run the folder scan maintenance task."""
        from src.server.services.scheduler.folder_scan_service import FolderScanService

        folder_scan_service = FolderScanService()
        await folder_scan_service.run_folder_scan()
        logger.info("Folder scan completed successfully")

    # ------------------------------------------------------------------
    # Step 4: Key resolution
    # ------------------------------------------------------------------

    async def _run_key_resolution(self) -> dict:
        """Run the orphaned folder key resolution scan.

        Returns:
            Dict with resolved/skipped/errors counts.
        """
        from src.server.services.scheduler.key_resolution_service import (
            perform_key_resolution_scan,
        )

        key_stats = await perform_key_resolution_scan()
        logger.info(
            "Key resolution scan completed: resolved=%d, skipped=%d, errors=%d",
            key_stats["resolved"],
            key_stats["skipped"],
            key_stats["errors"],
        )
        return key_stats

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    async def _broadcast(self, event_type: str, data: dict) -> None:
        """Broadcast a WebSocket event to all connected clients."""
        try:
            from src.server.services.websocket_service import get_websocket_service

            ws_service = get_websocket_service()
            await ws_service.manager.broadcast({"type": event_type, "data": data})
        except Exception as exc:
            logger.warning(
                "WebSocket broadcast failed: event=%s error=%s", event_type, exc
            )


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

_rescan_service: Optional[RescanService] = None


def get_rescan_service(config: Optional[SchedulerConfig] = None) -> RescanService:
    """Return a RescanService singleton (or create with optional config)."""
    global _rescan_service
    if _rescan_service is None or config is not None:
        _rescan_service = RescanService(config=config)
        logger.debug("Created new RescanService singleton")
    else:
        logger.debug("Returning existing RescanService singleton")
    return _rescan_service


def reset_rescan_service() -> None:
    """Reset the singleton (used in tests)."""
    global _rescan_service
    _rescan_service = None