"""
.mo file search functionality for wittrans.

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

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

import re
from multiprocessing import cpu_count
from multiprocessing.pool import Pool
from pathlib import Path
from typing import List, Optional, Tuple

import polib
from rich import print as rprint
from rich.progress import Progress

from .models import FileResult, TranslationMatch


def find_mo_files(base_path: Path) -> List[Path]:
    """
    Find all .mo files in the given directory and its subdirectories.

    Args:
        base_path: Directory path to search in

    Returns:
        List of paths to .mo files
    """
    try:
        return list(base_path.rglob("*.mo"))
    except PermissionError:
        rprint(
            f"[yellow]Warning: Permission denied accessing some files in {base_path}[/yellow]"
        )
        return []
    except Exception as e:
        rprint(f"[yellow]Warning: Error scanning {base_path}: {str(e)}[/yellow]")
        return []


def search_mo_file_wrapper(
    args: Tuple[Path, str, bool, bool, bool],
) -> FileResult:
    """
    Wrapper function to enable `search_mo_file` to be used with `multiprocessing.Pool.imap`.

    This function unpacks a tuple of arguments and passes them to `search_mo_file`.
    The `multiprocessing.Pool.imap` method can only accept functions that take a single
    argument, so this wrapper combines the `file_path`, `search_term`, and search options
    into a single tuple parameter that gets unpacked.

    Args:
        args: A tuple containing:
            - file_path (Path): Path to the .mo file to search
            - search_term (str): Text to search for in the .mo file
            - search_source (bool): Whether to search in source strings
            - search_translation (bool): Whether to search in translation strings
            - whole_word (bool): Whether to match whole words only

    Returns:
        A FileResult containing the file path and the list of TranslationMatch items
        found in the file. The matches list is empty if no matches are found.

    Note:
        This function always returns a FileResult, even when no matches are found
        (in which case the matches list will be empty).
    """
    file_path, search_term, search_source, search_translation, whole_word = args
    return FileResult(
        file_path,
        search_mo_file(
            file_path, search_term, search_source, search_translation, whole_word
        ),
    )


def search_directory(
    base_path: Path,
    search_term: str,
    progress: Progress,
    search_source: bool = True,
    search_translation: bool = True,
    whole_word: bool = False,
    pool: Optional[Pool] = None,
) -> Tuple[List[FileResult], int]:
    """
    Search for translations in all .mo files in a directory using multiprocessing.

    Uses a process pool to search each .mo file in parallel, updating the progress bar
    incrementally as each file is processed.

    Args:
        base_path: Directory path to search in.
        search_term: Text to search for (case-insensitive, in both original and translated text).
        progress: Progress bar instance for reporting search progress.
        search_source: Whether to search in source strings (default: True).
        search_translation: Whether to search in translation strings (default: True).
        whole_word: Whether to match whole words only (default: False).
        pool: Optional multiprocessing.Pool instance to reuse.

    Returns:
        Tuple containing:
            - List of FileResult items, one per .mo file that had matches.
            - int: Number of .mo files found in this directory.

        If no .mo files are found in the directory, returns ([], 0) and no progress task is created.
    """
    mo_files = find_mo_files(base_path)
    mo_file_count = len(mo_files)

    if not mo_files:
        return [], 0

    results = []
    task = progress.add_task(f"[cyan]Searching in {base_path}...", total=mo_file_count)

    # Prepare arguments for the pool
    args_list = [
        (file, search_term, search_source, search_translation, whole_word)
        for file in mo_files
    ]

    def _collect_results(imap_iterator):
        for file_path, matches in imap_iterator:
            if matches:
                results.append(FileResult(file_path, matches))
            progress.advance(task)

    if pool:
        _collect_results(pool.imap(search_mo_file_wrapper, args_list))
    else:
        with Pool(processes=cpu_count()) as local_pool:
            _collect_results(local_pool.imap(search_mo_file_wrapper, args_list))

    return results, mo_file_count


def search_mo_file(
    file_path: Path,
    search_term: str,
    search_source: bool = True,
    search_translation: bool = True,
    whole_word: bool = False,
) -> List[TranslationMatch]:
    """
    Search for translations in a single .mo file.

    Args:
        file_path: Path to the .mo file
        search_term: Text to search for
        search_source: Whether to search in source strings
        search_translation: Whether to search in translation strings
        whole_word: Whether to match whole words only

    Returns:
        List of TranslationMatch items found in the file
    """
    try:
        mo = polib.mofile(str(file_path), encoding="utf8")
        matches = []

        # Initialize for ty
        pattern: Optional[re.Pattern[str]] = None
        search_lower: str = ""

        # Prepare search pattern
        if whole_word:
            # regex pattern for whole word matching (case-insensitive)
            # \b is word boundary, escape to prevent search term to mess regex
            pattern = re.compile(r"\b" + re.escape(search_term) + r"\b", re.IGNORECASE)
        else:
            # For partial matching, convert to lowercase for comparison
            search_lower = search_term.lower()

        for entry in mo:
            match_found = False

            if search_source:
                if whole_word:
                    # if pattern.search(entry.msgid):
                    if pattern and pattern.search(entry.msgid):
                        match_found = True
                else:
                    if search_lower in entry.msgid.lower():
                        match_found = True

            if search_translation and not match_found:
                if whole_word:
                    if pattern and pattern.search(entry.msgstr):
                        match_found = True
                else:
                    if search_lower in entry.msgstr.lower():
                        match_found = True

            if match_found:
                matches.append(
                    TranslationMatch(entry.msgid, entry.msgstr, entry.msgctxt)
                )

        return matches
    except Exception as e:
        rprint(f"[red]Error processing file {file_path}: {str(e)}[/red]")
        return []
