"""Persistence of manifest.json / config.json / deployed_files.json, with file locking and atomic writes."""

import json
import os
import tempfile
from pathlib import Path
from typing import Any

from filelock import FileLock

from core import constants
from core.logger_config import logger

_LOCK_PATH = constants.CONFIG_FILE.parent / ".storage.lock"


def _atomic_json_write(file_path: Path, data: Any) -> None:
    """Writes data to a JSON file in a clean, atomic manner."""
    file_path.parent.mkdir(parents=True, exist_ok=True)
    _LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)

    with FileLock(str(_LOCK_PATH), timeout=10):
        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(
                "w", dir=str(file_path.parent), delete=False, encoding="utf-8"
            ) as tf:
                json.dump(data, tf, indent=4, ensure_ascii=False)
                tmp_file = Path(tf.name)

            os.replace(tmp_file, file_path)
        except (OSError, TypeError, ValueError) as e:
            if tmp_file and tmp_file.exists():
                tmp_file.unlink()
            raise


# ==================================================
#             Config File Management
# ==================================================
def initialisation_config_file() -> dict[str, Any]:
    """Basic initialization of the config.json file with the correct structure."""
    default_config = {"load_order": [], "mod_commands": {}}
    if not constants.CONFIG_FILE.exists():
        with constants.CONFIG_FILE.open("w", encoding="utf-8") as f:
            json.dump(default_config, f, indent=4, ensure_ascii=False)
    return default_config


def load_config() -> dict[str, Any]:
    """Load the global configuration (only the load order now)."""
    if not constants.CONFIG_FILE.exists():
        initialisation_config_file()
    with constants.CONFIG_FILE.open(encoding="utf-8") as f:
        return json.load(f)


def save_config(config_data: dict[str, Any]) -> None:
    """Save the global configuration (only the load order now)."""
    _atomic_json_write(constants.CONFIG_FILE, config_data)


# ==================================================
#             Deploy Manifest Management
# ==================================================
def load_deployed_manifest() -> dict[str, list[str]]:
    """Loads the manifest of the files currently deployed on the server."""
    if not constants.DEPLOY_MANIFEST.exists():
        return {}
    with constants.DEPLOY_MANIFEST.open(encoding="utf-8") as f:
        try:
            return json.load(f)
        except json.JSONDecodeError:
            return {}


def save_deployed_manifest(manifest_data: dict[str, list[str]]) -> None:
    """Saves the manifest of the deployed files."""
    _atomic_json_write(constants.DEPLOY_MANIFEST, manifest_data)


# ==================================================
#             Data Manifest Management
# ==================================================
def get_or_create_manifest(mod_path: Path, mod_id: str) -> dict[str, Any]:
    """Retrieves or generates a basic manifest."""
    manifest_path = mod_path / "manifest.json"
    if not manifest_path.exists():
        logger.info(
            f"[Auto-Init] manifest.json is missing for {mod_id}. Creating a default file..."
        )
        manifest = {
            "id": mod_id,
            "name": mod_id.replace("_", " ").title(),
            "target_category": "Normal",
            "version": "1.0.0",
            "description": "Automatically imported mod",
            "author": "Inconnu",
            "config_files": {},
        }
    else:
        with manifest_path.open(encoding="utf-8") as f:
            manifest = json.load(f)

    if "config_files" not in manifest:
        manifest["config_files"] = {}

    return manifest


def save_manifest(mod_path: Path, manifest_data: dict[str, Any]) -> None:
    """Save the manifest.json file."""
    _atomic_json_write(mod_path / "manifest.json", manifest_data)


def save_form_values_to_manifests(form_data: dict[str, str]) -> list[str]:
    """Utility function to extract CVARs from the form and update individual manifests."""
    mods_to_update: dict[str, dict[str, Any]] = {}

    for key, value in form_data.items():
        if ":" in key:
            mod_id, cvar_name = key.split(":", 1)
            mods_to_update.setdefault(mod_id, {})[cvar_name] = _parse_form_value(value)

    updated_mod_ids = []

    # Application des changements par manifest
    for mod_id, cvars_changes in mods_to_update.items():
        mod_path = constants.REPOSITORY / mod_id
        manifest_path = mod_path / "manifest.json"

        if not manifest_path.exists():
            continue

        with manifest_path.open(encoding="utf-8") as f:
            manifest = json.load(f)

        updated = False
        for cvars_list in manifest.get("config_files", {}).values():
            for cvar_entry in cvars_list:
                cvar_name = cvar_entry["cvar"]
                if cvar_name in cvars_changes:
                    cvar_entry["value"] = cvars_changes[cvar_name]
                    updated = True

        if updated:
            save_manifest(mod_path, manifest)
            updated_mod_ids.append(mod_id)

    return updated_mod_ids


def _parse_form_value(value: str) -> Any:
    """Converts a raw HTTP form string to its corresponding Python type."""
    if value == "true":
        return True
    if value == "false":
        return False
    try:
        return float(value) if "." in value else int(value)
    except ValueError:
        return value


# ==================================================
#            Data Manifest Fields Management
# ==================================================


def update_manifest_field(mod_id: str, field_name: str, value: Any) -> bool:
    """Updates or adds a specific field to the first level of the manifest."""
    mod_path = constants.REPOSITORY / mod_id

    manifest = get_or_create_manifest(mod_path, mod_id)

    if manifest.get(field_name) == value:
        return False

    manifest[field_name] = value
    save_manifest(mod_path, manifest)
    return True


# ==================================================
#                   Toggle Mod
# ==================================================


def toggle_mod_load_order(mod_id: str) -> bool:
    """Enables or disables a mod in the global load order."""
    config = load_config()
    load_order = config.setdefault("load_order", [])

    if mod_id in load_order:
        load_order.remove(mod_id)
        is_active = False
    else:
        load_order.append(mod_id)
        is_active = True

    save_config(config)
    return is_active


# ==================================================
#                   Link/Unlink
# ==================================================


def link_cfg_to_manifest(mod_id: str, cfg_path: str) -> None:
    """Associate a .cfg path with a mod's manifest."""
    if not cfg_path:
        return

    mod_path = constants.REPOSITORY / mod_id
    manifest = get_or_create_manifest(mod_path, mod_id)

    if cfg_path not in manifest.get("config_files", {}):
        manifest.setdefault("config_files", {})[cfg_path] = []
        save_manifest(mod_path, manifest)


def unlink_cfg_from_manifest(mod_id: str, cfg_path: str) -> bool:
    """Removes a .cfg path from a mod's manifest and deletes the associated files."""
    if not cfg_path:
        return False

    mod_path = constants.REPOSITORY / mod_id
    manifest_path = mod_path / "manifest.json"

    if not manifest_path.exists():
        return False

    with manifest_path.open(encoding="utf-8") as f:
        manifest = json.load(f)

    if "config_files" in manifest and cfg_path in manifest["config_files"]:
        del manifest["config_files"][cfg_path]
        save_manifest(mod_path, manifest)
        return True

    return False
