"""SQLite repository layer for campaign persistence and queries.

Primary entry point is open_campaign_db(), which opens/initializes
the database and returns all repositories in a single call.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING

from unfamiliar.db.connection import DatabaseError, init_database, open_database
from unfamiliar.db.repositories import (
    CampaignRepository,
    ChainRepository,
    FileSnapshotRepository,
    MissionFileRefRepository,
    MissionLinkRepository,
    MissionRepository,
    ModuleRepository,
)

if TYPE_CHECKING:
    import sqlite3
    from pathlib import Path

logger = logging.getLogger(__name__)

__all__ = [
    "CampaignDB",
    "CampaignRepository",
    "ChainRepository",
    "DatabaseError",
    "FileSnapshotRepository",
    "MissionRepository",
    "ModuleRepository",
    "open_campaign_db",
]


@dataclass(frozen=True)
class CampaignDB:
    """Container for an open campaign database and its repositories

    Provides named access to the connection and all repositories
    Supports context manager protocol for automatic connection cleanup

    NOTE: Long-lived consumers (e.g., Textual apps) may intentionally
    bypass the context manager and call close() manually. This is expected
    for async widget lifecycles that don't support `with` blocks.

    Usage:
        with open_campaign_db(campaign_dir) as db:
            campaign = db.campaigns.create(Campaign(...))
            missions = db.missions.get_by_campaign(campaign.id)
        # Connection automatically closed, even on exception
    """

    conn: sqlite3.Connection
    campaign_repo: CampaignRepository
    module_repo: ModuleRepository
    mission_repo: MissionRepository
    mission_file_ref_repo: MissionFileRefRepository
    mission_link_repo: MissionLinkRepository
    chain_repo: ChainRepository
    snapshot_repo: FileSnapshotRepository

    def __enter__(self) -> CampaignDB:
        """Enter context manager, returning self"""
        return self

    def __exit__(self, *args: object) -> None:
        """Exit context manager, closing the connection"""
        self.conn.close()


def open_campaign_db(
    campaign_dir: Path,
    repo_path: str | None = None,
) -> CampaignDB:
    """Open/initialize a campaign database and return all repositories.

    This is the primary entry point for the db package. It:
    1. Opens or creates the SQLite database in the campaign directory
    2. Initializes the schema if the database is new
    3. Creates the .repo_path marker file if repo_path is provided
    4. Returns the connection and all repository instances

    Args:
        campaign_dir: Path to the campaign directory
                      (e.g., ~/.unfamiliar/campaigns/<name>-<hash>/)
        repo_path: Absolute path to the repository. If provided and the
                   .repo_path marker doesn't exist, it will be created.

    Returns:
        A CampaignDB dataclass with named fields for the connection
        and all repositories. Use as a context manager for automatic cleanup

    Raises:
        DatabaseError: If the directory doesn't exist or isn't writable.

    Example:
        >>> with open_campaign_db(campaign_dir, repo_path=str(repo)) as db:
        ...     campaign = db.campaign_repo.create(Campaign(repo_path=str(repo)))
        ...     missions = db.mission_repo.get_by_campaign(campaign.id)
    """
    db_path = campaign_dir / "campaign.db"

    # Open and initialize the database
    conn = open_database(db_path)
    init_database(conn)

    # Create .repo_path marker if repo_path is provided and marker doesn't exist
    if repo_path:
        marker_path = campaign_dir / ".repo_path"
        if not marker_path.exists():
            try:
                marker_path.write_text(repo_path)
            except OSError as exc:
                # Non-fatal - marker file is for convenience only
                logger.debug("Could not write .repo_path marker: %s", exc)

    # Create all repositories
    return CampaignDB(
        conn=conn,
        campaign_repo=CampaignRepository(conn),
        module_repo=ModuleRepository(conn),
        mission_repo=MissionRepository(conn),
        mission_file_ref_repo=MissionFileRefRepository(conn),
        mission_link_repo=MissionLinkRepository(conn),
        chain_repo=ChainRepository(conn),
        snapshot_repo=FileSnapshotRepository(conn),
    )
