"""Manifest entry application helpers."""

from __future__ import annotations

from pathlib import Path
import shutil

from revo_module_installer.cli_support.executor import resolve_entry_source_path
from revo_module_installer.config.manifest import (
    ManifestAnchorMatchMode,
    ManifestEntry,
    ManifestExactMatchMode,
    ManifestPatternMatchMode,
    MatchMode,
)


def _copy_entry(source_path: Path, destination_path: Path, overwrite: bool) -> None:
    """Copy a file or directory to the destination path.

    Args:
        source_path: Source file or directory.
        destination_path: Destination in workspace.
        overwrite: Whether existing paths should be replaced.
    """
    if source_path.is_dir():
        if destination_path.exists():
            if not overwrite:
                return
            if destination_path.is_file():
                destination_path.unlink()
        destination_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copytree(source_path, destination_path, dirs_exist_ok=overwrite)
        return

    if destination_path.exists():
        if not overwrite:
            return
        if destination_path.is_dir():
            shutil.rmtree(destination_path)
    destination_path.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(source_path, destination_path)


def _replace_text_by_match_mode(
    target_text: str,
    replacement_text: str,
    match_mode: MatchMode,
) -> str:
    """Replace content in target text according to match mode.

    Args:
        target_text: Original target text.
        replacement_text: Replacement text from source artifact.
        match_mode: Matching mode definition.

    Returns:
        Updated target text.
    """
    if isinstance(match_mode, ManifestExactMatchMode):
        return replacement_text

    if isinstance(match_mode, ManifestPatternMatchMode):
        return target_text.replace(match_mode.value, replacement_text)

    anchor_mode: ManifestAnchorMatchMode = match_mode
    start_index: int = target_text.find(anchor_mode.start_pattern)
    end_index: int = target_text.find(anchor_mode.end_pattern)
    if start_index == -1 or end_index == -1 or end_index < start_index:
        return target_text
    content_start_index: int = start_index + len(anchor_mode.start_pattern)
    return (
        target_text[:content_start_index]
        + "\n"
        + replacement_text
        + "\n"
        + target_text[end_index:]
    )


def apply_manifest_entries(
    manifest_entries: list[ManifestEntry],
    manifest_source_url: str,
    workspace_path: Path,
) -> int:
    """Apply manifest entries to the workspace.

    Args:
        manifest_entries: Entries to apply.
        manifest_source_url: URL used to load the manifest.
        workspace_path: Workspace root where files are applied.

    Returns:
        Number of entries processed.
    """
    clone_cache: dict[tuple[str, str], Path] = {}
    for manifest_entry in manifest_entries:
        source_path: Path = resolve_entry_source_path(
            manifest_entry,
            manifest_source_url,
            clone_cache,
        )
        target_relative_path: str = (
            manifest_entry.target_path
            if manifest_entry.target_path is not None
            else manifest_entry.artifact_path
        )
        destination_path: Path = workspace_path / target_relative_path

        if manifest_entry.action == "add":
            _copy_entry(source_path, destination_path, overwrite=False)
            continue
        if manifest_entry.action == "add_overwrite":
            _copy_entry(source_path, destination_path, overwrite=True)
            continue

        source_text: str = source_path.read_text(encoding="utf-8")
        destination_path.parent.mkdir(parents=True, exist_ok=True)
        target_text: str = ""
        if destination_path.exists():
            target_text = destination_path.read_text(encoding="utf-8")

        if manifest_entry.action == "append_if_missing":
            if source_text in target_text:
                continue
            separator: str = ""
            if target_text:
                if target_text.endswith("\n\n"):
                    separator = ""
                elif target_text.endswith("\n"):
                    separator = "\n"
                else:
                    separator = "\n\n"
            destination_path.write_text(
                target_text + separator + source_text,
                encoding="utf-8",
            )
            continue

        if manifest_entry.action == "append_replace":
            if manifest_entry.match_mode is None:
                raise ValueError(
                    "Entry "
                    f"{manifest_entry.id} requires match_mode for append_replace.",
                )
            replaced_text: str = _replace_text_by_match_mode(
                target_text,
                source_text,
                manifest_entry.match_mode,
            )
            destination_path.write_text(replaced_text, encoding="utf-8")
            continue

        raise ValueError(f"Unsupported action: {manifest_entry.action}")

    return len(manifest_entries)
