"""Install plan helpers for the CLI."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Final, TypedDict

import httpx
from platformdirs import user_cache_path

from revo_module_installer.config.load import load_manifest_index
from revo_module_installer.config.manifest import ManifestHistoryEntry, ManifestIndex
from revo_module_installer.config.registry import RegistryEntry

GITHUB_TAGS_API_TEMPLATE: Final[str] = "https://api.github.com/repos/{repository}/tags"


class InstallPlanEntry(TypedDict):
    """Install plan entry derived from a manifest entry."""

    id: str
    action: str
    artifact_path: str
    target_path: str | None
    ref_type: str | None
    ref_value: str | None
    match_mode_type: str | None


class InstallPlan(TypedDict):
    """Install plan for a module."""

    module_id: str
    module_name: str
    registry_version: str
    manifest_version: str
    entries: list[InstallPlanEntry]


def build_install_plan(
    registry_entry: RegistryEntry,
    manifest_index: ManifestIndex,
    registry_version: str,
) -> InstallPlan:
    """Build an install plan from registry and manifest metadata.

    Args:
        registry_entry: Registry metadata for the module.
        manifest_index: Parsed manifest data.
        registry_version: Version of the registry index.

    Returns:
        Install plan dictionary.
    """
    plan_entries: list[InstallPlanEntry] = []
    for manifest_entry in manifest_index.entries:
        match_mode_type: str | None = None
        if manifest_entry.match_mode is not None:
            match_mode_type = manifest_entry.match_mode.type
        plan_entries.append(
            {
                "id": manifest_entry.id,
                "action": manifest_entry.action,
                "artifact_path": manifest_entry.artifact_path,
                "target_path": manifest_entry.target_path,
                "ref_type": manifest_entry.ref_type,
                "ref_value": manifest_entry.ref_value,
                "match_mode_type": match_mode_type,
            },
        )

    return {
        "module_id": registry_entry.id,
        "module_name": registry_entry.name,
        "registry_version": registry_version,
        "manifest_version": manifest_index.version,
        "entries": plan_entries,
    }


def resolve_manifest_source_url(
    registry_entry: RegistryEntry,
    manifest_url_override: str | None,
    *,
    selected_variant_id: str | None = None,
) -> str:
    """Resolve the manifest URL based on override and selected variant.

    Args:
        registry_entry: Registry metadata for the module.
        manifest_url_override: Optional manifest URL override.
        selected_variant_id: Optional selected variant identifier.

    Returns:
        Manifest URL that should be loaded.
    """
    if manifest_url_override is not None:
        return manifest_url_override

    if (
        selected_variant_id is None
        or selected_variant_id == registry_entry.default_variant
    ):
        return registry_entry.manifest_url

    for variant in registry_entry.variants:
        if variant.id == selected_variant_id:
            return variant.manifest_url

    return registry_entry.manifest_url


def _fetch_github_tags(repository: str) -> list[str]:
    """Fetch all GitHub tags for a repository.

    Args:
        repository: Repository slug in owner/name format.

    Returns:
        Tag names in API order.
    """
    tags_api_url: str = GITHUB_TAGS_API_TEMPLATE.format(repository=repository)
    headers: dict[str, str] = {"Accept": "application/vnd.github+json"}
    tag_names: list[str] = []
    page_number: int = 1
    while True:
        response: httpx.Response = httpx.get(
            tags_api_url,
            params={"per_page": 100, "page": page_number},
            headers=headers,
            timeout=10.0,
        )
        response.raise_for_status()
        payload: list[dict[str, object]] = response.json()
        if not payload:
            break
        for tag_object in payload:
            tag_name: object = tag_object.get("name")
            if isinstance(tag_name, str) and tag_name.startswith("v"):
                tag_names.append(tag_name)
        page_number += 1
    return tag_names


def _build_manifest_history_from_registry(
    registry_entry: RegistryEntry,
    selected_variant_id: str | None,
) -> list[ManifestHistoryEntry]:
    """Build manifest history entries from registry history settings.

    Args:
        registry_entry: Registry metadata for the module.
        selected_variant_id: Optional selected variant identifier.

    Returns:
        Manifest history entries loaded from GitHub tags.
    """
    if registry_entry.history is None:
        return []

    history_file_path: str = registry_entry.history.file_path
    if (
        selected_variant_id is not None
        and selected_variant_id in registry_entry.history.variant_files
    ):
        history_file_path = registry_entry.history.variant_files[selected_variant_id]

    tag_names: list[str] = _fetch_github_tags(registry_entry.history.repository)
    return [
        ManifestHistoryEntry(
            version=tag_name,
            manifest_url=(
                "https://raw.githubusercontent.com/"
                f"{registry_entry.history.repository}/{tag_name}/{history_file_path}"
            ),
        )
        for tag_name in tag_names
    ]


def resolve_manifest_index(
    registry_entry: RegistryEntry,
    manifest_url_override: str | None,
    *,
    selected_variant_id: str | None = None,
) -> ManifestIndex:
    """Resolve the manifest index for a registry entry.

    Args:
        registry_entry: Registry metadata for the module.
        manifest_url_override: Optional manifest URL override.
        selected_variant_id: Optional selected variant identifier.

    Returns:
        Parsed manifest index.
    """
    selected_manifest_url: str = resolve_manifest_source_url(
        registry_entry,
        manifest_url_override,
        selected_variant_id=selected_variant_id,
    )
    manifest_index: ManifestIndex = load_manifest_index(selected_manifest_url)
    history_entries: list[ManifestHistoryEntry] = _build_manifest_history_from_registry(
        registry_entry,
        selected_variant_id,
    )
    if not history_entries:
        return manifest_index
    return manifest_index.model_copy(update={"manifest_history": history_entries})


def default_plan_path(module_id: str) -> Path:
    """Return the default install plan output path.

    Args:
        module_id: Module identifier to scope the plan.

    Returns:
        Path to the install plan file.
    """
    cache_root: Path = user_cache_path("revo-modules")
    return cache_root / "plans" / f"{module_id}.json"


def write_install_plan(plan: InstallPlan, output_path: Path) -> None:
    """Write the install plan to a JSON file.

    Args:
        plan: Install plan to serialize.
        output_path: Path to the output JSON file.
    """
    output_path.parent.mkdir(parents=True, exist_ok=True)
    payload: str = json.dumps(plan, indent=2, sort_keys=True)
    output_path.write_text(payload, encoding="utf-8")
