"""Workflow for discovering, initializing, and scanning mods in the repository."""

import json
import os
import shutil
from pathlib import Path
from typing import Any

from core import constants
from core.io.cfg_format import append_cvars_to_file, parse_cfg_file
from core.io.json_store import get_or_create_manifest, load_config, save_manifest
from core.io.structure import ensure_mod_structure
from core.io.umc_parser import parse_umc_file
from core.logger_config import logger


def initialize_and_scan_mod(mod_id: str) -> None:
    """Organizes the mod, extracts its CVARs from .cfg files, and updates the manifest."""
    mod_path = constants.REPOSITORY / mod_id
    if not mod_path.exists():
        logger.error(f"The folder for mod {mod_id} does not exist in the repository.")
        return

    ensure_mod_structure(mod_path, mod_id)
    manifest = get_or_create_manifest(mod_path, mod_id)
    cfg_files = collect_cfg_files_to_scan(mod_path, mod_id, manifest)
    manifest = process_cfg_extractions(cfg_files, manifest, mod_path, mod_id)
    save_manifest(mod_path, manifest)

    logger.info(f"[Scan] Initialization and scan of [ {mod_id} ] completed successfully!\n")


def collect_cfg_files_to_scan(
    mod_path: Path, mod_id: str, manifest: dict[str, Any]
) -> list[tuple[Path, str]]:
    """Scan and group the configuration files to be analyzed."""
    mod_cstrike = mod_path / "cstrike"

    logger.info("[Scan] Scanning for configuration files...")
    cfg_files_to_scan: list[tuple[Path, str]] = []

    # Étape A & B : Local & Plugins
    local_cfgs = _find_local_cfg_files(mod_cstrike / "cfg")
    cfg_files_to_scan.extend((path, "local") for path in local_cfgs)

    plugin_names = _extract_plugin_names(mod_cstrike / "addons" / "sourcemod" / "plugins")

    # Étape C : Serveur
    server_cfgs = _scan_server_matching_cfgs(constants.SERVER_CSTRIKE / "cfg", mod_id, plugin_names)
    for path in server_cfgs:
        if not any(f[0] == path for f in cfg_files_to_scan):
            cfg_files_to_scan.append((path, "server"))

    # Étape D : Sécurité pour les exceptions
    # Si la clé existe déjà dans le manifest, on force la vérification sur le serveur
    for explicit_path in list(manifest["config_files"].keys()):
        try:
            rel_path = Path(explicit_path).relative_to("cstrike")
            server_file_path = constants.SERVER_CSTRIKE / rel_path
            if server_file_path.exists() and not any(
                f[0] == server_file_path for f in cfg_files_to_scan
            ):
                logger.info(f"[Scan] Forced exception via the manifest : {explicit_path}")
                cfg_files_to_scan.append((server_file_path, "server"))
        except ValueError:
            pass

    return cfg_files_to_scan


def _find_local_cfg_files(local_cfg_dir: Path) -> list[Path]:
    """Returns a list of .cfg files located locally within the mod."""
    if not local_cfg_dir.exists():
        return []
    return [
        Path(root) / file
        for root, _, files in os.walk(local_cfg_dir)
        for file in files
        if file.endswith(".cfg") and not file.endswith(".cfg.base")
    ]


def _extract_plugin_names(plugins_dir: Path) -> list[str]:
    """Returns the lowercase names of all .smx plugins in the mod."""
    if not plugins_dir.exists():
        return []
    return [
        Path(file).stem.lower()
        for _, _, files in os.walk(plugins_dir)
        for file in files
        if file.endswith(".smx")
    ]


def _scan_server_matching_cfgs(
    server_cfg_dir: Path, mod_id: str, plugin_names: list[str]
) -> list[Path]:
    """Find the server's .cfg files associated with the mod_id or plugins."""
    if not server_cfg_dir.exists():
        return []

    matching_files = []
    for root, _, files in os.walk(server_cfg_dir):
        for file in files:
            if file.endswith(".cfg") and not file.endswith(".cfg.base"):
                cfg_stem = Path(file).stem.lower()
                if mod_id.lower() in cfg_stem or cfg_stem in plugin_names:
                    matching_files.append(Path(root) / file)
    return matching_files


def process_cfg_extractions(
    cfg_files: list[tuple[Path, str]], manifest: dict[str, Any], mod_path: Path, mod_id: str
) -> dict[str, Any]:
    """Manages extraction, server-to-local-repository synchronization, and manifest enrichment."""
    if not cfg_files:
        logger.info(f"[Scan] No .cfg files found for {mod_id}.")
        return manifest

    for file_path, source in cfg_files:
        base_dir = mod_path / "cstrike" if source == "local" else constants.SERVER_CSTRIKE
        relative_path_obj = Path("cstrike") / file_path.relative_to(base_dir)
        relative_cfg_str = relative_path_obj.as_posix()

        target_path = file_path

        if source == "server":
            target_path = _sync_server_file_to_local(
                file_path, mod_id, relative_path_obj, relative_cfg_str
            )

        if "addons/sourcemod/configs" in relative_cfg_str:
            manifest.setdefault("config_files", {})[relative_cfg_str] = []
            continue

        logger.info(f"[Scan] File detected ({source}) : {relative_cfg_str}")

        extracted_cvars = parse_cfg_file(target_path)
        old_manifest_cvars = manifest["config_files"].get(relative_cfg_str, [])

        synchronized_cvars = _merge_and_sync_cvars(old_manifest_cvars, extracted_cvars)

        added_count = len(
            [c for c in extracted_cvars if c["cvar"] not in {o["cvar"] for o in old_manifest_cvars}]
        )
        removed_count = len(old_manifest_cvars) - (len(synchronized_cvars) - added_count)
        if added_count > 0 or removed_count > 0:
            logger.info(
                f"[Scan Sync] {relative_cfg_str} -> Added: {added_count} | Removed obsolete: {removed_count}"
            )

        manifest["config_files"][relative_cfg_str] = synchronized_cvars

    return manifest


def _append_new_cvars_to_file(file_path: Path, local_dest_cfg: Path, relative_cfg_str: str) -> None:
    """Analyzes the differences in CVARs and inserts the new variables at the end of the file."""
    server_cvars = parse_cfg_file(file_path)
    local_cvars = parse_cfg_file(local_dest_cfg)

    local_cvar_names = {c["cvar"] for c in local_cvars}
    new_cvars = [c for c in server_cvars if c["cvar"] not in local_cvar_names]

    if new_cvars:
        logger.info(
            f"[Scan] Update detected on the server for {relative_cfg_str}! {len(new_cvars)} new variable(s) found."
        )
        append_cvars_to_file(local_dest_cfg, new_cvars)


def _sync_server_file_to_local(
    file_path: Path, mod_id: str, relative_path_obj: Path, relative_cfg_str: str
) -> Path:
    """Manages local imports, the creation of immutable databases, and data enrichment during updates."""
    local_dest_cfg = constants.REPOSITORY / mod_id / relative_path_obj
    local_dest_base = (
        constants.REPOSITORY / mod_id / str(relative_path_obj).replace(".cfg", ".cfg.base")
    )

    local_dest_cfg.parent.mkdir(parents=True, exist_ok=True)

    if local_dest_cfg.exists():
        _append_new_cvars_to_file(file_path, local_dest_cfg, relative_cfg_str)
        shutil.copy2(file_path, local_dest_base)

    if not local_dest_base.exists():
        shutil.copy2(file_path, local_dest_base)
        logger.info(f"[Scan] Created pristine base copy: {relative_cfg_str}.base")

    if not local_dest_cfg.exists():
        shutil.copy2(file_path, local_dest_cfg)
        logger.info(f"[Scan] Created modifiable local copy: {relative_cfg_str}")

    return local_dest_cfg


def _merge_and_sync_cvars(
    manifest_cvars: list[dict[str, Any]], extracted_cvars: list[dict[str, Any]]
) -> list[dict[str, Any]]:
    """Updates the manifest: adds new CVARs and removes obsolete ones."""
    current_values = {item["cvar"]: item.get("value", item["default"]) for item in manifest_cvars}

    updated_cvars = []
    for new_cvar in extracted_cvars:
        cvar_name = new_cvar["cvar"]
        if cvar_name in current_values:
            new_cvar["value"] = current_values[cvar_name]
        else:
            new_cvar["value"] = new_cvar["default"]
        updated_cvars.append(new_cvar)

    return updated_cvars


def get_available_server_cfgs() -> list[str]:
    """Returns a list of all .cfg files available in the server's cstrike/cfg/sourcemod directory."""
    server_sourcemod_cfg = constants.SERVER_CSTRIKE / "cfg" / "sourcemod"
    available_server_cfgs: list[str] = []

    if server_sourcemod_cfg.exists():
        for root, _, files in os.walk(server_sourcemod_cfg):
            for file in files:
                if file.endswith(".cfg") and not file.endswith(".cfg.base"):
                    relative_path = Path(root).relative_to(constants.SERVER_ROOT) / file
                    available_server_cfgs.append(relative_path.as_posix())

    return available_server_cfgs


def get_available_mods() -> list[dict[str, Any]]:
    """Returns a list of all mod IDs present in the repository."""
    if not constants.REPOSITORY.exists():
        return []

    config = load_config()
    load_order = config.get("load_order", [])
    config_commands = config.get("mod_commands", {})
    available_mods: list[dict[str, Any]] = []

    for element in constants.REPOSITORY.iterdir():
        if element.is_dir():
            mod_id = element.name
            manifest_path = constants.REPOSITORY / mod_id / "manifest.json"
            mod_key_clean = mod_id.strip().lower()

            cmd_list = config_commands.get(mod_key_clean, [])
            commands_str = "; ".join(cmd_list) if isinstance(cmd_list, list) else ""

            if not manifest_path.exists():
                available_mods.append(
                    {
                        "id": mod_id,
                        "name": mod_id,
                        "target_category": "Normal",
                        "commands_string": commands_str,
                        "initialized": False,
                        "active": mod_id in load_order,
                        "config_files": {},
                    }
                )
            else:
                with manifest_path.open(encoding="utf-8") as f:
                    manifest = json.load(f)
                    manifest["initialized"] = True
                    manifest["active"] = mod_id in load_order
                    manifest["commands_string"] = commands_str
                    available_mods.append(manifest)

    return available_mods


def get_used_mods() -> set[str]:
    """Returns only the mods currently referenced in the UMC."""
    if not constants.UMC_MASTER_FILE.exists():
        return []
    umc_data = parse_umc_file(constants.UMC_MASTER_FILE)
    used_ids: set[str] = set()
    for category in umc_data.values():
        for map_options in category.get("maps", {}).values():
            target_mod = map_options.get("target_mods")
            if target_mod:
                used_ids.add(target_mod)
    return used_ids


def get_maps_for_mod(mod_id: str) -> dict[str, list[str]]:
    """Returns every category containing maps linked to the given target_mod."""
    if not constants.UMC_MASTER_FILE.exists():
        return {}
    umc_data = parse_umc_file(constants.UMC_MASTER_FILE)
    result: dict[str, list[str]] = {}
    for category_name, category in umc_data.items():
        maps = [
            map_name
            for map_name, map_options in category.get("maps", {}).items()
            if map_options.get("target_mods") == mod_id
        ]
        if maps:
            result[category_name] = sorted(maps)
    return result
