"""Data queries — bridge from DB to view data.

Functions that query the database and return view dataclasses.
Each function:
1. Calls the appropriate repository methods
2. Transforms DB models into view dataclasses
3. Returns pure data (no Textual types)

This layer enables:
- Testing widgets without database
- Clear separation of concerns
- Easy mocking for TUI development
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from unfamiliar.campaign.progress import get_campaign_progress
from unfamiliar.constants import MissionStatus
from unfamiliar.tui.views import (
    BriefingData,
    CategoryProgressData,
    ChainSummary,
    FileRefData,
    HeaderData,
    MissionDetail,
    MissionSummary,
    StatusData,
    get_category_display_name,
)

if TYPE_CHECKING:
    from unfamiliar.db import CampaignDB
    from unfamiliar.models import Campaign, Mission


def load_header_data(db: CampaignDB, campaign: Campaign) -> HeaderData:
    """Load data for the investigation header widget.

    Args:
        db: Open campaign database with all repositories.
        campaign: The campaign model.

    Returns:
        HeaderData with campaign info and progress stats.
    """
    assert campaign.id is not None, "Campaign must be persisted"
    progress = get_campaign_progress(db, campaign.id)

    repo_name = Path(campaign.repo_path).name
    theme_name = "Film Noir"

    return HeaderData(
        campaign_name=f"Investigation: {repo_name}",
        repo_name=repo_name,
        theme_name=theme_name,
        completed=progress.completed_missions,
        total=progress.total_missions,
        available=progress.available_missions,
    )


def load_status_data(db: CampaignDB, campaign: Campaign) -> StatusData:
    """Load data for the case board (status) view.

    Args:
        db: Open campaign database with all repositories.
        campaign: The campaign model.

    Returns:
        StatusData with comprehensive campaign progress.
    """
    assert campaign.id is not None, "Campaign must be persisted"
    progress = get_campaign_progress(db, campaign.id)

    # Convert CategoryProgress to CategoryProgressData
    category_data = [
        CategoryProgressData(
            name=cp.category.value,
            display_name=get_category_display_name(cp.category.value),
            completed=cp.completed,
            total=cp.total,
            available=cp.available,
            locked=cp.locked,
            percent=cp.percent,
        )
        for cp in progress.category_progress
    ]

    # Load chains
    assert campaign.id is not None, "Campaign must be persisted"
    chains = db.chain_repo.get_by_campaign(campaign.id)
    chain_summaries = []
    for chain in chains:
        assert chain.id is not None, "Chain must be persisted"
        links = db.chain_repo.get_chain_missions(chain.id)
        missions = [db.mission_repo.get(link.mission_id) for link in links]
        completed_count = sum(
            1 for m in missions if m and m.status == MissionStatus.COMPLETED
        )
        chain_summaries.append(
            ChainSummary(
                name=chain.name,
                name_plain=chain.name_plain,
                category=get_category_display_name(chain.category.value),
                mission_count=len(links),
                completed_count=completed_count,
            )
        )

    repo_name = Path(campaign.repo_path).name

    return StatusData(
        campaign_name=f"Investigation: {repo_name}",
        overall_percent=progress.overall_percent,
        completed=progress.completed_missions,
        total=progress.total_missions,
        available=progress.available_missions,
        locked=progress.locked_missions,
        in_progress=progress.in_progress_missions,
        security_total=progress.security_total,
        security_completed=progress.security_completed,
        categories=category_data,
        chains=chain_summaries,
    )


def load_available_missions(db: CampaignDB, campaign_id: int) -> list[MissionSummary]:
    """Load summaries of available (unlocked, not started) missions.

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

    Returns:
        List of MissionSummary for available missions.
    """
    missions = db.mission_repo.get_by_status(campaign_id, MissionStatus.AVAILABLE)
    return [_mission_to_summary(m) for m in missions]


def load_actionable_missions(db: CampaignDB, campaign_id: int) -> list[MissionSummary]:
    """Load missions the user can act on (in-progress + available).

    Returns IN_PROGRESS missions first, then AVAILABLE, so active
    missions are at the top of the list.

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

    Returns:
        List of MissionSummary with in-progress first, then available.
    """
    # Load in-progress first
    in_progress = db.mission_repo.get_by_status(campaign_id, MissionStatus.IN_PROGRESS)
    # Then available
    available = db.mission_repo.get_by_status(campaign_id, MissionStatus.AVAILABLE)

    # Combine: in-progress first, then available
    summaries = [_mission_to_summary(m) for m in in_progress]
    summaries.extend(_mission_to_summary(m) for m in available)
    return summaries


def load_in_progress_missions(db: CampaignDB, campaign_id: int) -> list[MissionSummary]:
    """Load summaries of in-progress missions.

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

    Returns:
        List of MissionSummary for in-progress missions.
    """
    missions = db.mission_repo.get_by_status(campaign_id, MissionStatus.IN_PROGRESS)
    return [_mission_to_summary(m) for m in missions]


def load_mission_detail(db: CampaignDB, mission_id: int) -> MissionDetail:
    """Load detailed data for a single mission.

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

    Returns:
        MissionDetail with full mission information.

    Raises:
        ValueError: If mission not found.
    """
    mission = db.mission_repo.get(mission_id)
    if mission is None:
        raise ValueError(f"Mission {mission_id} not found")

    assert mission.id is not None, "Mission must be persisted"

    # Load file references
    file_refs = db.mission_file_ref_repo.get_file_refs(mission_id)
    file_ref_data = [
        FileRefData(
            file_path=ref.file_path,
            line_start=ref.line_start,
            line_end=ref.line_end,
            description=ref.description,
        )
        for ref in file_refs
    ]

    # Load prerequisites
    prereq_ids = db.mission_file_ref_repo.get_prerequisites(mission_id)
    prereq_titles = []
    for prereq_id in prereq_ids:
        prereq = db.mission_repo.get(prereq_id)
        if prereq:
            prereq_titles.append(prereq.title)

    return MissionDetail(
        id=mission.id,
        title=mission.title,
        title_plain=mission.title_plain,
        description=mission.description,
        category=get_category_display_name(mission.category.value),
        difficulty=mission.difficulty.value.replace("_", " ").title(),
        time_minutes=mission.time_estimate_minutes,
        status=mission.status.value,
        completion_condition=mission.completion_condition,
        security_tag=mission.security_tag,
        narrative_intro=mission.narrative_intro,
        narrative_outro=mission.narrative_outro,
        file_refs=file_ref_data,
        prerequisites=prereq_titles,
    )


def load_briefing_data(db: CampaignDB, campaign_id: int) -> BriefingData:
    """Load data for the briefing (case file) view.

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

    Returns:
        BriefingData with codebase summary. Returns placeholder
        if no briefing has been generated yet.
    """
    briefing = db.campaign_repo.get_briefing(campaign_id)

    if briefing is None:
        return BriefingData(
            summary=(
                "No briefing generated yet. Start an investigation to generate one."
            ),
            tech_stack=[],
            security_posture="",
        )

    return BriefingData(
        summary=briefing.summary,
        tech_stack=briefing.tech_stack,
        security_posture=briefing.security_posture,
    )


def load_chain_summaries(db: CampaignDB, campaign_id: int) -> list[ChainSummary]:
    """Load summaries of all chains in a campaign.

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

    Returns:
        List of ChainSummary for all chains.
    """
    chains = db.chain_repo.get_by_campaign(campaign_id)
    summaries = []

    for chain in chains:
        assert chain.id is not None, "Chain must be persisted"
        links = db.chain_repo.get_chain_missions(chain.id)
        missions = [db.mission_repo.get(link.mission_id) for link in links]
        completed_count = sum(
            1 for m in missions if m and m.status == MissionStatus.COMPLETED
        )

        summaries.append(
            ChainSummary(
                name=chain.name,
                name_plain=chain.name_plain,
                category=get_category_display_name(chain.category.value),
                mission_count=len(links),
                completed_count=completed_count,
            )
        )

    return summaries


def _mission_to_summary(mission: Mission) -> MissionSummary:
    """Convert a Mission model to a MissionSummary dataclass."""
    assert mission.id is not None, "Mission must be persisted"
    return MissionSummary(
        id=mission.id,
        title=mission.title,
        title_plain=mission.title_plain,
        category=get_category_display_name(mission.category.value),
        difficulty=mission.difficulty.value.replace("_", " ").title(),
        time_minutes=mission.time_estimate_minutes,
        status=mission.status.value,
        security_tag=mission.security_tag,
    )


__all__ = [
    "load_header_data",
    "load_status_data",
    "load_available_missions",
    "load_actionable_missions",
    "load_in_progress_missions",
    "load_mission_detail",
    "load_briefing_data",
    "load_chain_summaries",
]
