"""Mission repository.

Provides typed CRUD operations for Mission entities and related
junction tables: file references, prerequisites, and module links.
"""

from __future__ import annotations

import sqlite3
from datetime import UTC, datetime

from pydantic import TypeAdapter

from unfamiliar.constants import (
    CompletionMethod,
    InvestigationCategory,
    MissionDifficulty,
    MissionStatus,
)
from unfamiliar.db.connection import DatabaseError
from unfamiliar.models import Mission

# Type adapters for parsing database rows into models
_mission_adapter = TypeAdapter(Mission)


class MissionRepository:
    """Repository for Mission entities and related junction tables.

    Handles:
    - Mission CRUD with status/category filtering
    For file references, prerequisites, and module links,
    use MissionFileRefRepository and MissionLinkRepository.
    """

    def __init__(self, conn: sqlite3.Connection) -> None:
        """Initialize repository with a database connection.

        Args:
            conn: A sqlite3.Connection with Row factory enabled.
        """
        self._conn = conn

    # --- Mission CRUD ---

    def create(self, mission: Mission) -> Mission:
        """Insert a new mission and return it with id populated.

        Args:
            mission: A Mission without an id.

        Returns:
            The Mission with id and timestamps set.

        Raises:
            DatabaseError: On insert failure.
        """
        now = datetime.now(UTC).isoformat()
        try:
            cursor = self._conn.execute(
                """
                INSERT INTO mission (
                    campaign_id, title, title_plain, description, category,
                    difficulty, time_estimate_minutes, completion_condition,
                    security_tag, standard_tag, status, completion_method,
                    narrative_intro, narrative_outro, created_at, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    mission.campaign_id,
                    mission.title,
                    mission.title_plain,
                    mission.description,
                    str(mission.category),
                    str(mission.difficulty),
                    mission.time_estimate_minutes,
                    mission.completion_condition,
                    1 if mission.security_tag else 0,
                    mission.standard_tag,
                    str(mission.status),
                    (
                        str(mission.completion_method)
                        if mission.completion_method
                        else None
                    ),
                    mission.narrative_intro,
                    mission.narrative_outro,
                    now,
                    now,
                ),
            )
            self._conn.commit()
            return _mission_adapter.validate_python(
                {
                    **mission.model_dump(),
                    "id": cursor.lastrowid,
                    "created_at": now,
                    "updated_at": now,
                }
            )
        except sqlite3.Error as exc:
            raise DatabaseError("Failed to create mission") from exc

    def create_many(self, missions: list[Mission]) -> list[Mission]:
        """Bulk insert missions and return them with ids populated.

        Args:
            missions: List of Missions without ids.

        Returns:
            List of Missions with ids and timestamps set.

        Raises:
            DatabaseError: On insert failure.
        """
        if not missions:
            return []

        now = datetime.now(UTC).isoformat()
        results = []

        try:
            for m in missions:
                cursor = self._conn.execute(
                    """
                    INSERT INTO mission (
                        campaign_id, title, title_plain, description, category,
                        difficulty, time_estimate_minutes, completion_condition,
                        security_tag, standard_tag, status, completion_method,
                        narrative_intro, narrative_outro, created_at, updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    RETURNING id, created_at, updated_at
                    """,
                    (
                        m.campaign_id,
                        m.title,
                        m.title_plain,
                        m.description,
                        str(m.category),
                        str(m.difficulty),
                        m.time_estimate_minutes,
                        m.completion_condition,
                        1 if m.security_tag else 0,
                        m.standard_tag,
                        str(m.status),
                        str(m.completion_method) if m.completion_method else None,
                        m.narrative_intro,
                        m.narrative_outro,
                        now,
                        now,
                    ),
                )
                row = cursor.fetchone()
                results.append(
                    Mission(
                        id=row["id"],
                        campaign_id=m.campaign_id,
                        title=m.title,
                        title_plain=m.title_plain,
                        description=m.description,
                        category=m.category,
                        difficulty=m.difficulty,
                        time_estimate_minutes=m.time_estimate_minutes,
                        completion_condition=m.completion_condition,
                        security_tag=m.security_tag,
                        standard_tag=m.standard_tag,
                        status=m.status,
                        completion_method=m.completion_method,
                        narrative_intro=m.narrative_intro,
                        narrative_outro=m.narrative_outro,
                        created_at=row["created_at"],
                        updated_at=row["updated_at"],
                    )
                )
            self._conn.commit()
        except sqlite3.Error as exc:
            self._conn.rollback()
            raise DatabaseError("Failed to create missions") from exc
        return results

    def get(self, mission_id: int) -> Mission | None:
        """Fetch a mission by id.

        Args:
            mission_id: The mission's primary key.

        Returns:
            The Mission or None if not found.
        """
        row = self._conn.execute(
            """
            SELECT id, campaign_id, title, title_plain, description, category,
                   difficulty, time_estimate_minutes, completion_condition,
                   security_tag, standard_tag, status, completion_method,
                   narrative_intro, narrative_outro, created_at, updated_at
            FROM mission WHERE id = ?
            """,
            (mission_id,),
        ).fetchone()
        return _row_to_mission(row) if row else None

    def get_by_campaign(self, campaign_id: int) -> list[Mission]:
        """Fetch all missions for a campaign.

        Args:
            campaign_id: The parent campaign's id.

        Returns:
            List of Missions (may be empty).
        """
        rows = self._conn.execute(
            """
            SELECT id, campaign_id, title, title_plain, description, category,
                   difficulty, time_estimate_minutes, completion_condition,
                   security_tag, standard_tag, status, completion_method,
                   narrative_intro, narrative_outro, created_at, updated_at
            FROM mission WHERE campaign_id = ? ORDER BY title_plain
            """,
            (campaign_id,),
        ).fetchall()
        return [_row_to_mission(row) for row in rows]

    def get_by_status(self, campaign_id: int, status: MissionStatus) -> list[Mission]:
        """Fetch missions filtered by status.
        Args:
            campaign_id: The parent campaign's id.
            status: The status to filter by.

        Returns:
            List of Missions with the specified status.
        """
        rows = self._conn.execute(
            """
            SELECT id, campaign_id, title, title_plain, description, category,
                   difficulty, time_estimate_minutes, completion_condition,
                   security_tag, standard_tag, status, completion_method,
                   narrative_intro, narrative_outro, created_at, updated_at
            FROM mission
            WHERE campaign_id = ? AND status = ?
            ORDER BY title_plain
            """,
            (campaign_id, str(status)),
        ).fetchall()
        return [_row_to_mission(row) for row in rows]

    def get_by_ids(self, ids: list[int]) -> list[Mission]:
        """Fetch multiple missions by ID in a single query."""
        if not ids:
            return []
        placeholders = ",".join("?" * len(ids))
        cursor = self._conn.execute(
            f"""
            SELECT id, campaign_id, title, title_plain, description, category,
                   difficulty, time_estimate_minutes, completion_condition,
                   security_tag, standard_tag, status, completion_method,
                   narrative_intro, narrative_outro, created_at, updated_at
            FROM mission WHERE id IN ({placeholders})
            """,
            ids,
        )
        return [_row_to_mission(row) for row in cursor.fetchall()]

    def get_by_category(
        self, campaign_id: int, category: InvestigationCategory
    ) -> list[Mission]:
        """Fetch missions filtered by category.

        Args:
            campaign_id: The parent campaign's id.
            category: The category to filter by.

        Returns:
            List of Missions with the specified category.
        """
        rows = self._conn.execute(
            """
            SELECT id, campaign_id, title, title_plain, description, category,
                   difficulty, time_estimate_minutes, completion_condition,
                   security_tag, standard_tag, status, completion_method,
                   narrative_intro, narrative_outro, created_at, updated_at
            FROM mission
            WHERE campaign_id = ? AND category = ?
            ORDER BY title_plain
            """,
            (campaign_id, str(category)),
        ).fetchall()
        return [_row_to_mission(row) for row in rows]

    def update_status(
        self,
        mission_id: int,
        status: MissionStatus,
        completion_method: CompletionMethod | None = None,
    ) -> None:
        """Update only the status field of a mission.

        Args:
            mission_id: The mission's primary key.
            status: The new status value.
            completion_method: How the mission was completed (if COMPLETED).

        Raises:
            DatabaseError: If the mission doesn't exist.
        """
        now = datetime.now(UTC).isoformat()
        try:
            cursor = self._conn.execute(
                """
                UPDATE mission SET status = ?, completion_method = ?, updated_at = ?
                WHERE id = ?
                """,
                (
                    str(status),
                    str(completion_method) if completion_method else None,
                    now,
                    mission_id,
                ),
            )
            self._conn.commit()
            if cursor.rowcount == 0:
                raise DatabaseError(f"No mission found with id: {mission_id}")
        except sqlite3.Error as exc:
            raise DatabaseError("Failed to update mission status") from exc

    def count_by_campaign(self, campaign_id: int) -> int:
        """Count total missions for a campaign.

        Args:
            campaign_id: The parent campaign's id.

        Returns:
            Total number of missions in the campaign.
        """
        row = self._conn.execute(
            "SELECT COUNT(*) as count FROM mission WHERE campaign_id = ?",
            (campaign_id,),
        ).fetchone()
        return row["count"] if row else 0

    def count_completed(self, campaign_id: int) -> int:
        """Count completed missions for a campaign.

        Args:
            campaign_id: The parent campaign's id.

        Returns:
            Number of missions with COMPLETED status.
        """
        row = self._conn.execute(
            "SELECT COUNT(*) as count FROM mission "
            "WHERE campaign_id = ? AND status = ?",
            (campaign_id, str(MissionStatus.COMPLETED)),
        ).fetchone()
        return row["count"] if row else 0


def _row_to_mission(row: sqlite3.Row) -> Mission:
    """Convert a database row to a Mission model."""
    return _mission_adapter.validate_python(
        {
            "id": row["id"],
            "campaign_id": row["campaign_id"],
            "title": row["title"],
            "title_plain": row["title_plain"],
            "description": row["description"],
            "category": InvestigationCategory(row["category"]),
            "difficulty": MissionDifficulty(row["difficulty"]),
            "time_estimate_minutes": row["time_estimate_minutes"],
            "completion_condition": row["completion_condition"],
            "security_tag": bool(row["security_tag"]),
            "standard_tag": row["standard_tag"],
            "status": MissionStatus(row["status"]),
            "completion_method": (
                CompletionMethod(row["completion_method"])
                if row["completion_method"]
                else None
            ),
            "narrative_intro": row["narrative_intro"],
            "narrative_outro": row["narrative_outro"],
            "created_at": row["created_at"],
            "updated_at": row["updated_at"],
        }
    )
