"""Mission status engine — lifecycle management for investigation missions.

Handles status transitions, completion, unlocking of and prerequisite checking
for missions within a campaign.

Status flow:
- Root missions (no prerequisites) start as AVAILABLE
- Non-root missions start as LOCKED
- Completing a mission unlocks dependents if all their prerequisites are met
- Escape hatch: unlock_mission() forces any mission to AVAILABLE
"""

import logging

from unfamiliar.campaign.types import CompletionResult
from unfamiliar.constants import CampaignStatus, CompletionMethod, MissionStatus
from unfamiliar.db import CampaignDB

logger = logging.getLogger(__name__)


def _mission_preflight_check(mission_id: int, db: CampaignDB) -> None:
    mission = db.mission_repo.get(mission_id)
    if mission is None:
        raise MissionStatusError(
            mission_id,
            None,
            f"Mission {mission_id} not found",
        )

    if mission.status == MissionStatus.COMPLETED:
        raise MissionStatusError(
            mission_id,
            mission.status,
            f"Mission {mission_id} is already completed",
        )

    if mission.status == MissionStatus.LOCKED:
        raise MissionStatusError(
            mission_id,
            mission.status,
            f"Mission {mission_id} is LOCKED",
        )


class MissionStatusError(Exception):
    """Raised when a mission status transition is invalid."""

    def __init__(
        self, mission_id: int, current_status: MissionStatus | None, message: str
    ):
        super().__init__(message)
        self.mission_id = mission_id
        self.current_status = current_status


class CampaignStatusError(Exception):
    """Raised when a campaign status transition is invalid."""

    def __init__(
        self, campaign_id: int, current_status: CampaignStatus | None, message: str
    ):
        super().__init__(message)
        self.campaign_id = campaign_id
        self.current_status = current_status


def complete_mission(
    db: CampaignDB,
    mission_id: int,
) -> CompletionResult:
    """Mark a mission as completed via self-report and unlock dependents.

    Finds all missions that depend on this mission (have it as a prerequisite).
    For each dependent, checks if ALL its prerequisites are now COMPLETED.
    If yes, transitions the dependent from LOCKED to AVAILABLE.

    Args:
        db: Open campaign database with all repositories.
        mission_id: ID of the mission to complete.

    Returns:
        CompletionResult with mission_id and list of newly unlocked mission IDs.

    Raises:
        MissionStatusError: If mission not found, already completed, or LOCKED.
    """
    _mission_preflight_check(mission_id, db)

    # Update to completed with SELF_REPORT
    db.mission_repo.update_status(
        mission_id,
        MissionStatus.COMPLETED,
        CompletionMethod.SELF_REPORT,
    )
    logger.info("Mission %d marked as COMPLETED", mission_id)

    # Find and unlock eligible dependents
    unlocked_ids = _unlock_eligible_dependents(db, mission_id)
    logger.debug(
        "complete_mission returning: mission_id=%d, unlocked_ids=%s",
        mission_id,
        unlocked_ids,
    )

    return CompletionResult(mission_id=mission_id, unlocked_ids=unlocked_ids)


def unlock_mission(db: CampaignDB, mission_id: int) -> None:
    """Force a locked mission to available status (escape hatch).

    Allows users to bypass prerequisite requirements when stuck
    or when prerequisites are no longer relevant.

    Args:
        db: Open campaign database with all repositories.
        mission_id: ID of the mission to unlock.

    Raises:
        MissionStatusError: If mission not found or already COMPLETED.
    """
    mission = db.mission_repo.get(mission_id)
    if mission is None:
        raise MissionStatusError(
            mission_id,
            None,
            f"Mission {mission_id} not found",
        )

    if mission.status == MissionStatus.COMPLETED:
        raise MissionStatusError(
            mission_id,
            mission.status,
            f"Mission {mission_id} is already COMPLETED and cannot be unlocked",
        )

    # AVAILABLE and IN_PROGRESS missions are no-ops
    if mission.status in (MissionStatus.AVAILABLE, MissionStatus.IN_PROGRESS):
        logger.debug("Mission %d is already %s, no-op", mission_id, mission.status)
        return

    # LOCKED -> AVAILABLE
    db.mission_repo.update_status(mission_id, MissionStatus.AVAILABLE)
    logger.info("Mission %d force-unlocked from LOCKED to AVAILABLE", mission_id)


def start_mission(db: CampaignDB, mission_id: int) -> None:
    """Mark an available mission as in progress.

    Args:
        db: Open campaign database with all repositories.
        mission_id: ID of the mission to start.

    Raises:
        MissionStatusError: If mission not found, LOCKED, or COMPLETED.
    """
    _mission_preflight_check(mission_id, db)

    # AVAILABLE -> IN_PROGRESS
    db.mission_repo.update_status(mission_id, MissionStatus.IN_PROGRESS)
    logger.info("Mission %d started", mission_id)


def reopen_campaign(db: CampaignDB, campaign_id: int) -> None:
    """Reopen a closed campaign.

    Changes campaign status from CLOSED to ACTIVE, allowing users to
    continue investigating a previously closed case.

    Args:
        db: Open campaign database with all repositories.
        campaign_id: ID of the campaign to reopen.

    Raises:
        MissionStatusError: If campaign not found or not CLOSED.
    """
    campaign = db.campaign_repo.get(campaign_id)
    if campaign is None:
        raise CampaignStatusError(
            campaign_id,
            None,
            f"Campaign {campaign_id} not found",
        )

    if campaign.status != CampaignStatus.CLOSED:
        raise CampaignStatusError(
            campaign_id,
            campaign.status,
            f"Campaign {campaign_id} is not CLOSED (status: {campaign.status})",
        )

    db.campaign_repo.update_status(campaign_id, CampaignStatus.ACTIVE)
    logger.info("Campaign %d reopened from CLOSED to ACTIVE", campaign_id)


def _unlock_eligible_dependents(db: CampaignDB, completed_mission_id: int) -> list[int]:
    """Find and unlock dependents whose prerequisites are all completed.

    Returns list of mission IDs that were transitioned from LOCKED to AVAILABLE.
    """
    unlocked_ids: list[int] = []

    # Get all missions that have completed_mission_id as a prerequisite
    dependent_ids = db.mission_file_ref_repo.get_dependents(completed_mission_id)
    logger.debug(
        "Checking %d dependents of completed mission %d",
        len(dependent_ids),
        completed_mission_id,
    )

    for dependent_id in dependent_ids:
        # Get all prerequisites for this dependent
        prerequisite_ids = db.mission_file_ref_repo.get_prerequisites(dependent_id)
        logger.debug(
            "Dependent %d has %d prerequisites: %s",
            dependent_id,
            len(prerequisite_ids),
            prerequisite_ids,
        )

        # Check if ALL prerequisites are now COMPLETED
        all_prereqs_completed = True
        for prereq_id in prerequisite_ids:
            prereq_mission = db.mission_repo.get(prereq_id)
            if (
                prereq_mission is None
                or prereq_mission.status != MissionStatus.COMPLETED
            ):
                logger.debug(
                    "Prerequisite %d not met (status: %s)",
                    prereq_id,
                    prereq_mission.status if prereq_mission else "MISSING",
                )
                all_prereqs_completed = False
                break

            else:
                logger.debug(
                    "Prerequisite %d satisfied (status: %s)",
                    prereq_id,
                    prereq_mission.status,
                )

        if all_prereqs_completed:
            # Check current status - only unlock if LOCKED
            dependent_mission = db.mission_repo.get(dependent_id)
            if dependent_mission is None:
                logger.debug(
                    "Dependent %d not found, skipping",
                    dependent_id,
                )
            elif dependent_mission.status == MissionStatus.LOCKED:
                db.mission_repo.update_status(
                    dependent_id,
                    MissionStatus.AVAILABLE,
                )
                unlocked_ids.append(dependent_id)
                logger.info(
                    "Mission %d unlocked (all prerequisites completed)",
                    dependent_id,
                )
            else:
                logger.debug(
                    "Dependent %d already %s, not unlocking",
                    dependent_id,
                    dependent_mission.status,
                )

    logger.debug(
        "Unlock cascade complete: %d of %d dependents unlocked",
        len(unlocked_ids),
        len(dependent_ids),
    )
    return unlocked_ids
