"""Resource resolution and loading helpers."""

from __future__ import annotations

from importlib.resources import files
from importlib.resources.abc import Traversable
import os
from pathlib import Path
from typing import Final
from urllib.parse import ParseResult, unquote, urlparse
from urllib.request import url2pathname

import httpx

from revo_module_installer.constants import REGISTRY_INDEX_FILENAME

LOCAL_EXAMPLE_MANIFEST_FILENAME: Final[str] = "example_manifest.yml"
LOCAL_EXAMPLE_REGISTRY_FILENAME: Final[str] = "example_registry.yml"
REQUEST_TIMEOUT_SECONDS: Final[float] = 10.0


def resolve_registry_index_url(registry_repository_url: str) -> str:
    """Resolve a registry index URL from a git repository location.

    Args:
        registry_repository_url: URL to the registry git repository.

    Returns:
        URL to the registry index YAML file.
    """
    parsed_repository_url: ParseResult = urlparse(registry_repository_url)
    if parsed_repository_url.netloc != "github.com":
        return registry_repository_url

    repository_path: str = parsed_repository_url.path.strip("/")
    if repository_path.endswith(".git"):
        repository_path = repository_path[: -len(".git")]

    return (
        f"https://raw.githubusercontent.com/{repository_path}/main/"
        f"{REGISTRY_INDEX_FILENAME}"
    )


def resolve_raw_github_url(resource_url: str) -> str:
    """Convert a GitHub blob URL to a raw content URL.

    Args:
        resource_url: URL that may point to a GitHub blob.

    Returns:
        Raw content URL if the input is a GitHub blob, otherwise the input.
    """
    parsed_url: ParseResult = urlparse(resource_url)
    if parsed_url.netloc in {"raw.githubusercontent.com", ""}:
        return resource_url

    if parsed_url.netloc != "github.com":
        return resource_url

    path_parts: list[str] = parsed_url.path.strip("/").split("/")
    if "blob" not in path_parts:
        return resource_url

    blob_index: int = path_parts.index("blob")
    if blob_index < 2 or blob_index + 2 >= len(path_parts):
        return resource_url

    repository_slug: str = "/".join(path_parts[:blob_index])
    ref_name: str = path_parts[blob_index + 1]
    file_path: str = "/".join(path_parts[blob_index + 2 :])
    return (
        f"https://raw.githubusercontent.com/{repository_slug}/{ref_name}/"
        f"{file_path}"
    )


def get_local_example_registry_path() -> Path:
    """Return the path to the bundled example registry file.

    Returns:
        Filesystem path to the example registry.
    """
    package_root: Path = Path(__file__).resolve().parents[1]
    return package_root / "examples" / LOCAL_EXAMPLE_REGISTRY_FILENAME


def get_local_example_manifest_path() -> Path:
    """Return the path to the bundled example manifest file.

    Returns:
        Filesystem path to the example manifest.
    """
    package_root: Path = Path(__file__).resolve().parents[1]
    return package_root / "examples" / LOCAL_EXAMPLE_MANIFEST_FILENAME


def _resolve_package_resource_name(parsed_url: ParseResult) -> str:
    """Resolve a resource name from a package URL.

    Args:
        parsed_url: Parsed package URL.

    Returns:
        Resource name within the package data directory.
    """
    if parsed_url.netloc:
        return parsed_url.netloc
    return parsed_url.path.lstrip("/")


def _load_text_from_package(resource_name: str) -> str:
    """Load text from a package example resource.

    Args:
        resource_name: Filename within the package examples directory.

    Returns:
        Text contents of the resource.
    """
    resource_path: Traversable = (
        files("revo_module_installer.examples") / resource_name
    )
    if not resource_path.is_file():
        message: str = f"Package resource not found: {resource_name}"
        raise FileNotFoundError(message)
    return resource_path.read_text(encoding="utf-8")


def _load_text_from_file(resource_path: Path) -> str:
    """Load text from a local file path.

    Args:
        resource_path: Path to the local file.

    Returns:
        Text contents of the file.
    """
    return resource_path.read_text(encoding="utf-8")


def resolve_file_url_path(parsed_url: ParseResult) -> Path:
    """Resolve a filesystem path from a file URL parse result.

    Args:
        parsed_url: Parsed file URL.

    Returns:
        Filesystem path for the file URL.
    """
    if parsed_url.netloc in {"", "localhost"}:
        url_path: str = parsed_url.path
    elif parsed_url.path:
        url_path = f"//{parsed_url.netloc}{parsed_url.path}"
    else:
        url_path = parsed_url.netloc
    if os.name == "nt":
        return Path(url2pathname(url_path))
    return Path(unquote(url_path))


def looks_like_windows_path(resource_url: str) -> bool:
    """Return True when a string resembles a Windows drive path.

    Args:
        resource_url: Raw resource string to inspect.

    Returns:
        True when the resource looks like a Windows drive path.
    """
    if len(resource_url) < 2:
        return False
    return resource_url[1] == ":" and resource_url[0].isalpha()


def load_text_from_location(resource_url: str) -> str:
    """Load text from HTTP, file, or package locations.

    Args:
        resource_url: URL or path to load.

    Returns:
        Text contents of the resource.
    """
    parsed_url: ParseResult = urlparse(resource_url)
    if parsed_url.scheme in {"http", "https"}:
        response: httpx.Response = httpx.get(
            resource_url,
            timeout=REQUEST_TIMEOUT_SECONDS,
        )
        response.raise_for_status()
        return response.text

    if parsed_url.scheme == "file":
        resource_path: Path = resolve_file_url_path(parsed_url)
        return _load_text_from_file(resource_path)

    if parsed_url.scheme == "package":
        resource_name: str = _resolve_package_resource_name(parsed_url)
        return _load_text_from_package(resource_name)

    if parsed_url.scheme and looks_like_windows_path(resource_url):
        return _load_text_from_file(Path(resource_url))

    resource_path: Path = Path(resource_url)
    if parsed_url.scheme == "" and resource_path.exists():
        return _load_text_from_file(resource_path)

    message: str = f"Unsupported resource location: {resource_url}"
    raise ValueError(message)
