"""Mission chain repository.

Provides typed CRUD operations for MissionChain entities and
their ordered links to missions.
"""

from __future__ import annotations

import sqlite3
from datetime import UTC, datetime

from pydantic import TypeAdapter

from unfamiliar.constants import InvestigationCategory
from unfamiliar.db.connection import DatabaseError
from unfamiliar.models import ChainMissionLink, MissionChain

# Type adapters for parsing database rows into models
_chain_adapter = TypeAdapter(MissionChain)
_chain_link_adapter = TypeAdapter(ChainMissionLink)


class ChainRepository:
    """Repository for MissionChain entities and chain-mission links.

    Handles:
    - Chain CRUD
    - Ordered mission links (position determines sequence)
    """

    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

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

        Args:
            chain: A MissionChain without an id.

        Returns:
            The MissionChain with id and timestamps set.

        Raises:
            DatabaseError: On insert failure.
        """
        now = datetime.now(UTC).isoformat()
        try:
            cursor = self._conn.execute(
                """
                INSERT INTO mission_chain (
                    campaign_id, name, name_plain, description, category,
                    sort_order, created_at, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    chain.campaign_id,
                    chain.name,
                    chain.name_plain,
                    chain.description,
                    str(chain.category),
                    chain.sort_order,
                    now,
                    now,
                ),
            )
            self._conn.commit()
            return _chain_adapter.validate_python(
                {
                    **chain.model_dump(),
                    "id": cursor.lastrowid,
                    "created_at": now,
                    "updated_at": now,
                }
            )
        except sqlite3.Error as exc:
            raise DatabaseError("Failed to create chain") from exc

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

        Args:
            chains: List of MissionChains without ids.

        Returns:
            List of MissionChains with ids and timestamps set.

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

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

        try:
            for c in chains:
                cursor = self._conn.execute(
                    """
                INSERT INTO mission_chain (
                    campaign_id, name, name_plain, description, category,
                    sort_order, created_at, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                    RETURNING id, created_at, updated_at
                """,
                    (
                        c.campaign_id,
                        c.name,
                        c.name_plain,
                        c.description,
                        str(c.category),
                        c.sort_order,
                        now,
                        now,
                    ),
                )
                row = cursor.fetchone()
                results.append(
                    MissionChain(
                        id=row["id"],
                        campaign_id=c.campaign_id,
                        name=c.name,
                        name_plain=c.name_plain,
                        description=c.description,
                        category=c.category,
                        sort_order=c.sort_order,
                        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 chains") from exc
        return results

    def get(self, chain_id: int) -> MissionChain | None:
        """Fetch a chain by id.

        Args:
            chain_id: The chain's primary key.

        Returns:
            The MissionChain or None if not found.
        """
        row = self._conn.execute(
            """
            SELECT id, campaign_id, name, name_plain, description, category,
                   sort_order, created_at, updated_at
            FROM mission_chain WHERE id = ?
            """,
            (chain_id,),
        ).fetchone()
        return _row_to_chain(row) if row else None

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

        Args:
            campaign_id: The parent campaign's id.

        Returns:
            List of MissionChains ordered by sort_order (may be empty).
        """
        rows = self._conn.execute(
            """
            SELECT id, campaign_id, name, name_plain, description, category,
                   sort_order, created_at, updated_at
            FROM mission_chain WHERE campaign_id = ? ORDER BY sort_order
            """,
            (campaign_id,),
        ).fetchall()
        return [_row_to_chain(row) for row in rows]

    def add_missions(self, chain_id: int, links: list[ChainMissionLink]) -> None:
        """Add missions to a chain in order.

        Args:
            chain_id: The chain's primary key.
            links: List of ChainMissionLink objects with positions.

        Raises:
            DatabaseError: On insert failure or invalid IDs.
        """
        if not links:
            return

        rows = [(chain_id, link.mission_id, link.position) for link in links]

        try:
            self._conn.executemany(
                """
                INSERT OR REPLACE INTO chain_mission (
                    chain_id, mission_id, position
                ) VALUES (?, ?, ?)
                """,
                rows,
            )
            self._conn.commit()
        except sqlite3.Error as exc:
            raise DatabaseError("Failed to add missions to chain") from exc

    def get_chain_missions(self, chain_id: int) -> list[ChainMissionLink]:
        """Get mission links for a chain, ordered by position.

        Args:
            chain_id: The chain's primary key.

        Returns:
            List of ChainMissionLink objects ordered by position.
        """
        rows = self._conn.execute(
            """
            SELECT chain_id, mission_id, position FROM chain_mission WHERE chain_id = ?
            ORDER BY position
            """,
            (chain_id,),
        ).fetchall()
        return [_row_to_chain_link(row) for row in rows]

    def get_chains_for_mission(self, mission_id: int) -> list[int]:
        """Get chain IDs that contain a mission.

        Args:
            mission_id: The mission's primary key.

        Returns:
            List of chain IDs.
        """
        rows = self._conn.execute(
            "SELECT chain_id FROM chain_mission WHERE mission_id = ?",
            (mission_id,),
        ).fetchall()
        return [row["chain_id"] for row in rows]


def _row_to_chain(row: sqlite3.Row) -> MissionChain:
    """Convert a database row to a MissionChain model."""
    return _chain_adapter.validate_python(
        {
            "id": row["id"],
            "campaign_id": row["campaign_id"],
            "name": row["name"],
            "name_plain": row["name_plain"],
            "description": row["description"],
            "category": InvestigationCategory(row["category"]),
            "sort_order": row["sort_order"],
            "created_at": row["created_at"],
            "updated_at": row["updated_at"],
        }
    )


def _row_to_chain_link(row: sqlite3.Row) -> ChainMissionLink:
    """Convert a database row to a ChainMissionLink model."""
    return _chain_link_adapter.validate_python(
        {
            "chain_id": row["chain_id"],
            "mission_id": row["mission_id"],
            "position": row["position"],
        }
    )
