"""
Translation path related functionality for wittrans.

SPDX-FileCopyrightText: 2024 Jiri Grönroos <jiri.gronroos@iki.fi>

SPDX-License-Identifier: LGPL-3.0-or-later
"""

import subprocess
from pathlib import Path
from typing import List, Set, Tuple

from .exceptions import InvalidLanguageCode, TranslationDirectoryNotFound
from .language import get_language_name
from .language_mappings import (
    iso_639_3_to_1,
    regional_variants,
)
from .models import TranslationPaths


def validate_language_paths(
    lang_code: str, include_flatpak: bool = True
) -> TranslationPaths:
    """
    Validate and return existing translation paths categorized by source type.

    Supports:
    1. ISO 639-1 codes (two letters, e.g., "fi", "sv", "en")
    2. ISO 639-2/3 codes (three letters, e.g., "fin", "swe", "eng")
    3. GNU C library locale codes (e.g., "fi_FI.utf8", "zh_CN", "pt_BR")

    Searches the following paths:
    - System translations: /usr/share/locale/, /usr/share/locale-langpack/, /usr/local/share/locale/
    - User translations: ~/.local/share/locale/
    - System GNOME Shell extensions: /usr/share/gnome-shell/extensions/, /usr/local/share/gnome-shell/extensions/
    - System KDE Plasmoids: /usr/share/plasma/plasmoids/, /usr/local/share/plasma/plasmoids/
    - User GNOME Shell extensions: ~/.local/share/gnome-shell/extensions/
    - User KDE Plasmoids: ~/.local/share/plasma/plasmoids/
    - Flatpak applications and runtimes (system-wide and user-specific) - if include_flatpak is True

    Args:
        lang_code: Language code (ISO 639-1, ISO 639-2/3, or full locale code)
        include_flatpak: Whether to search Flatpak applications (default: True)

    Returns:
        TranslationPaths named tuple with categorized paths

    Raises:
        InvalidLanguageCode: If language code is invalid
        TranslationDirectoryNotFound: If no translation directories exist
    """

    # Normalize the input code
    normalized_code = lang_code.lower().strip()

    # Split the code into parts (e.g., "fi_FI.utf8" -> ["fi", "FI", "utf8"])
    parts = normalized_code.split(".")
    locale_parts = parts[0].split("_")
    base_lang = locale_parts[0]

    # Validate the base language code
    if not base_lang.isalpha() or not (2 <= len(base_lang) <= 3):
        raise InvalidLanguageCode(
            f"Invalid language code: '{lang_code}'. The language code must be "
            "a two or three-letter ISO-639 code or locale (e.g., 'fi', 'fin', 'zh_CN')."
        )

    # Build list of codes to search for
    search_codes = set()

    # 1. Add the original code first (if it's a full locale code)
    if len(locale_parts) > 1:
        search_codes.add("_".join(locale_parts[:2]).lower())

    # 2. Check for regional variants
    if base_lang in regional_variants:
        search_codes.update(regional_variants[base_lang])

    # 3. Add base language code
    if len(base_lang) == 2:
        # For 2-letter codes, add as is
        search_codes.add(base_lang)
    elif len(base_lang) == 3:
        # For 3-letter codes, try to map to 2-letter code first
        two_letter = iso_639_3_to_1.get(base_lang)
        if two_letter:
            search_codes.add(two_letter)
        else:
            # If no 2-letter mapping exists, use the 3-letter code
            search_codes.add(base_lang)

    # Initialize result containers
    system_locale_paths = []
    user_locale_paths = []
    system_gnome_paths = []
    system_kde_paths = []
    user_gnome_paths = []
    user_kde_paths = []

    # Build system locale paths
    for code in search_codes:
        potential_paths = [
            Path(f"/usr/share/locale-langpack/{code}/LC_MESSAGES"),
            Path(f"/usr/share/locale/{code}/LC_MESSAGES"),
            Path(f"/usr/local/share/locale/{code}/LC_MESSAGES"),
        ]
        system_locale_paths.extend([p for p in potential_paths if p.exists()])

    # Build user locale paths
    for code in search_codes:
        potential_paths = [
            Path.home() / f".local/share/locale/{code}/LC_MESSAGES",
        ]
        user_locale_paths.extend([p for p in potential_paths if p.exists()])

    # Build system GNOME Shell extensions paths
    system_gnome_bases = [
        Path("/usr/share/gnome-shell/extensions"),  # System-installed
        Path(
            "/usr/local/share/gnome-shell/extensions"
        ),  # Not installed by package manager
    ]

    for gnome_base in system_gnome_bases:
        if gnome_base.exists():
            for extension_dir in gnome_base.iterdir():
                if extension_dir.is_dir():
                    for code in search_codes:
                        extension_locale_path = (
                            extension_dir / f"locale/{code}/LC_MESSAGES"
                        )
                        if extension_locale_path.exists():
                            system_gnome_paths.append(extension_locale_path)

    # Build user GNOME Shell extensions paths
    user_gnome_base = Path.home() / ".local/share/gnome-shell/extensions"
    if user_gnome_base.exists():
        for extension_dir in user_gnome_base.iterdir():
            if extension_dir.is_dir():
                for code in search_codes:
                    extension_locale_path = extension_dir / f"locale/{code}/LC_MESSAGES"
                    if extension_locale_path.exists():
                        user_gnome_paths.append(extension_locale_path)

    # Build system KDE Plasmoids paths
    system_kde_bases = [
        Path("/usr/share/plasma/plasmoids"),  # System-installed
        Path("/usr/local/share/plasma/plasmoids"),  # Not installed by package manager
    ]

    for kde_base in system_kde_bases:
        if kde_base.exists():
            for plasmoid_dir in kde_base.iterdir():
                if plasmoid_dir.is_dir():
                    for code in search_codes:
                        plasmoid_locale_path = (
                            plasmoid_dir / f"contents/locale/{code}/LC_MESSAGES"
                        )
                        if plasmoid_locale_path.exists():
                            system_kde_paths.append(plasmoid_locale_path)

    # Build user KDE Plasmoids paths
    user_kde_base = Path.home() / ".local/share/plasma/plasmoids"
    if user_kde_base.exists():
        for plasmoid_dir in user_kde_base.iterdir():
            if plasmoid_dir.is_dir():
                for code in search_codes:
                    plasmoid_locale_path = (
                        plasmoid_dir / f"contents/locale/{code}/LC_MESSAGES"
                    )
                    if plasmoid_locale_path.exists():
                        user_kde_paths.append(plasmoid_locale_path)

    # Get Flatpak paths (categorized by system/user) - only if enabled
    if include_flatpak:
        flatpak_system_paths, flatpak_user_paths = (
            get_flatpak_translation_paths_categorized(search_codes)
        )
    else:
        flatpak_system_paths = []
        flatpak_user_paths = []

    # Create the result tuple
    result = TranslationPaths(
        system_locale=system_locale_paths,
        user_locale=user_locale_paths,
        system_gnome_extensions=system_gnome_paths,
        system_kde_plasmoids=system_kde_paths,
        user_gnome_extensions=user_gnome_paths,
        user_kde_plasmoids=user_kde_paths,
        system_flatpak=flatpak_system_paths,
        user_flatpak=flatpak_user_paths,
    )

    # Check if any translations were found
    total_paths = sum(len(paths) for paths in result)

    if total_paths == 0:
        try:
            language_name = get_language_name(lang_code)
        except Exception:
            language_name = lang_code

        error_msg = [
            f"No translation directories found for {language_name} ({lang_code}).",
            "\nAttempted to find translations using these codes: "
            + ", ".join(sorted(search_codes)),
            "\nSearched in the following categories:",
            "- System locale directories",
            "- User locale directories",
            "- System GNOME Shell extensions",
            "- System KDE Plasmoids",
            "- User GNOME Shell extensions",
            "- User KDE Plasmoids",
            "- Flatpak applications (system and user)",
        ]

        raise TranslationDirectoryNotFound("\n".join(error_msg))

    return result


def get_flatpak_translation_paths_categorized(
    search_codes: Set[str],
) -> Tuple[List[Path], List[Path]]:
    """
    Get translation paths for installed Flatpak applications and runtimes, categorized by system/user.

    Args:
        search_codes: Set of language codes to search for

    Returns:
        Tuple of (system_flatpak_paths, user_flatpak_paths)
    """
    system_paths = []
    user_paths = []

    try:
        # Check if Flatpak is installed
        subprocess.run(
            ["flatpak", "--version"], capture_output=True, text=True, check=True
        )

        # System-wide Flatpak paths
        system_flatpak_bases = [
            Path("/var/lib/flatpak/app"),
            Path("/var/lib/flatpak/runtime"),
        ]
        for base in system_flatpak_bases:
            if base.exists():
                paths = get_flatpak_paths_from_base(base, search_codes)
                system_paths.extend(paths)

        # User-specific Flatpak paths
        user_flatpak_bases = [
            Path.home() / ".local/share/flatpak/app",
            Path.home() / ".local/share/flatpak/runtime",
        ]
        for base in user_flatpak_bases:
            if base.exists():
                paths = get_flatpak_paths_from_base(base, search_codes)
                user_paths.extend(paths)

    except (subprocess.CalledProcessError, FileNotFoundError):
        pass  # Flatpak not installed

    return system_paths, user_paths


def get_flatpak_paths_from_base(base_path: Path, search_codes: Set[str]) -> List[Path]:
    """
    Helper function to extract Flatpak translation paths from a base directory.

    Iterates all architecture and branch subdirectories to support any architecture
    (x86_64, aarch64, etc.) and any branch (stable, master, beta, etc.).
    Ignores paths that resolve to the same location via symlink (e.g. 'current').

    Args:
        base_path: Base Flatpak directory path
        search_codes: Set of language codes to search for

    Returns:
        List of existing translation paths
    """
    translation_paths = []
    seen_resolved: Set[Path] = set()

    for app_dir in base_path.iterdir():
        if not app_dir.is_dir():
            continue

        for arch_entry in app_dir.iterdir():
            if not arch_entry.is_dir():
                continue

            for branch_entry in arch_entry.iterdir():
                if not branch_entry.is_dir():
                    continue

                active_dir = branch_entry / "active"
                if not active_dir.exists():
                    continue

                for lang_code in search_codes:
                    # Common translation directory patterns
                    possible_paths = [
                        # Standard app paths
                        active_dir / f"files/share/locale/{lang_code}/LC_MESSAGES",
                        active_dir / f"files/locale/{lang_code}/LC_MESSAGES",
                        # Runtime-specific paths
                        active_dir / f"files/{lang_code}/share/{lang_code}/LC_MESSAGES",
                        active_dir / f"files/share/{lang_code}/LC_MESSAGES",
                        # Additional runtime paths
                        active_dir
                        / f"files/share/runtime/locale/{lang_code}/LC_MESSAGES",
                        active_dir / f"files/{lang_code}/share/locale/LC_MESSAGES",
                        # Some apps might use different patterns
                        active_dir / f"files/{lang_code}/LC_MESSAGES",
                    ]

                    for p in possible_paths:
                        if p.exists():
                            resolved = p.resolve()
                            if resolved not in seen_resolved:
                                seen_resolved.add(resolved)
                                translation_paths.append(p)

    return translation_paths


def get_flatpak_installation_dirs() -> Set[Path]:
    """
    Get all Flatpak installation directory paths (system-wide and user-specific).

    Returns:
        Set of paths to Flatpak installation directories
    """
    paths = set()

    try:
        # Check if Flatpak is installed
        subprocess.run(
            ["flatpak", "--version"], capture_output=True, text=True, check=True
        )

        # System-wide installation paths
        system_paths = [Path("/var/lib/flatpak/app"), Path("/var/lib/flatpak/runtime")]
        paths.update(p for p in system_paths if p.exists())

        # User-specific installation paths
        user_paths = [
            Path.home() / ".local/share/flatpak/app",
            Path.home() / ".local/share/flatpak/runtime",
        ]
        paths.update(p for p in user_paths if p.exists())

        return paths

    except (subprocess.CalledProcessError, FileNotFoundError):
        return set()
