"""Tests for core.services.delete_service."""

import json
import sys
import tempfile
import unittest
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from core import constants
from core.exceptions import DeleteServiceError
from core.io import json_store
from core.io.umc_parser import parse_umc_file
from core.io.umc_writer import write
from core.services import delete_service


class TestDeleteService(unittest.TestCase):
    def setUp(self) -> None:
        self.sandbox = tempfile.TemporaryDirectory()
        self.sandbox_path = Path(self.sandbox.name)

        constants.SERVER_ROOT = self.sandbox_path
        constants.SERVER_CSTRIKE = self.sandbox_path / "cstrike"
        constants.APP_DIR = self.sandbox_path / "css_mod_manager"
        constants.REPOSITORY = constants.APP_DIR / "repository"
        constants.CONFIG_FILE = constants.APP_DIR / "config.json"
        constants.DEPLOY_MANIFEST = constants.APP_DIR / "deployed_files.json"
        constants.UMC_MASTER_FILE = self.sandbox_path / "umc_mapcycle_master.txt"
        constants.UMC_FILE = constants.SERVER_CSTRIKE / "umc_mapcycle.txt"
        constants.MAPS_FOLDER = constants.SERVER_CSTRIKE / "download" / "maps"

        constants.REPOSITORY.mkdir(parents=True, exist_ok=True)
        constants.SERVER_CSTRIKE.mkdir(parents=True, exist_ok=True)
        constants.MAPS_FOLDER.mkdir(parents=True, exist_ok=True)

        json_store.save_config({"load_order": []})

    def tearDown(self) -> None:
        self.sandbox.cleanup()

    def _make_mod(
        self, mod_id: str, target_category: str | None = "Normal", active: bool = True
    ) -> Path:
        mod_dir = constants.REPOSITORY / mod_id
        mod_dir.mkdir(parents=True, exist_ok=True)
        manifest: dict[str, object] = {"id": mod_id, "name": mod_id}
        if target_category:
            manifest["target_category"] = target_category
        with (mod_dir / "manifest.json").open("w", encoding="utf-8") as f:
            json.dump(manifest, f)
        if active:
            config = json_store.load_config()
            config.setdefault("load_order", []).append(mod_id)
            json_store.save_config(config)
        return mod_dir

    def _write_master(self, umc_data: dict) -> None:
        write(constants.UMC_MASTER_FILE, umc_data, create_backup=False)

    # ------------------------------------------------------------------
    # validate_mod_deletion
    # ------------------------------------------------------------------

    def test_validate_rejects_empty_id(self) -> None:
        with self.assertRaises(DeleteServiceError):
            delete_service.validate_mod_deletion("")

    def test_validate_rejects_unknown_mod(self) -> None:
        with self.assertRaises(DeleteServiceError):
            delete_service.validate_mod_deletion("ghost_mod")

    def test_validate_rejects_mod_without_manifest(self) -> None:
        (constants.REPOSITORY / "no_manifest_mod").mkdir(parents=True)
        with self.assertRaises(DeleteServiceError):
            delete_service.validate_mod_deletion("no_manifest_mod")

    def test_validate_accepts_valid_mod(self) -> None:
        self._make_mod("valid_mod")
        delete_service.validate_mod_deletion("valid_mod")  # should not raise

    # ------------------------------------------------------------------
    # delete_mod
    # ------------------------------------------------------------------

    def test_delete_mod_removes_its_maps_and_unused_category(self) -> None:
        self._make_mod("solo_mod", target_category="GenericCategory")
        self._write_master(
            {"GenericCategory": {"options": {}, "maps": {"de_solo": {"target_mods": "solo_mod"}}}}
        )

        result = delete_service.delete_mod("solo_mod")

        self.assertEqual(result["status"], "success")
        self.assertFalse((constants.REPOSITORY / "solo_mod").exists())

        remaining = parse_umc_file(constants.UMC_MASTER_FILE)
        self.assertNotIn(
            "GenericCategory", remaining, "A non-permanent, now-empty category should be dropped."
        )

        config = json_store.load_config()
        self.assertNotIn("solo_mod", config["load_order"])

    def test_delete_mod_removes_category_when_not_shared(self) -> None:
        self._make_mod("only_owner", target_category="OnlyOwnerCategory")
        self._write_master(
            {"OnlyOwnerCategory": {"options": {}, "maps": {"de_x": {"target_mods": "only_owner"}}}}
        )

        delete_service.delete_mod("only_owner")

        remaining = parse_umc_file(constants.UMC_MASTER_FILE)
        self.assertNotIn("OnlyOwnerCategory", remaining)

    def test_delete_mod_never_removes_a_permanent_category(self) -> None:
        """Regression test: even as the sole owner of a permanent category
        (e.g. "Normal", per constants.PERMANENT_CATEGORIES), deleting a mod
        must not remove the category itself -- only its own maps -- since
        permanent categories can hold maps unrelated to any mod.
        """
        self._make_mod("solo_mod", target_category="Normal")
        self._write_master(
            {
                "Normal": {
                    "options": {},
                    "maps": {"de_solo": {"target_mods": "solo_mod"}, "de_untouched": {}},
                }
            }
        )

        delete_service.delete_mod("solo_mod")

        remaining = parse_umc_file(constants.UMC_MASTER_FILE)
        self.assertIn("Normal", remaining, "A permanent category must never be deleted.")
        self.assertNotIn("de_solo", remaining["Normal"]["maps"])
        self.assertIn("de_untouched", remaining["Normal"]["maps"])

    def test_delete_mod_keeps_category_when_shared(self) -> None:
        self._make_mod("mod_a", target_category="Shared")
        self._make_mod("mod_b", target_category="Shared")
        self._write_master(
            {
                "Shared": {
                    "options": {},
                    "maps": {"de_a": {"target_mods": "mod_a"}, "de_b": {"target_mods": "mod_b"}},
                }
            }
        )

        delete_service.delete_mod("mod_a")

        remaining = parse_umc_file(constants.UMC_MASTER_FILE)
        self.assertIn("Shared", remaining, "A category still used by another mod must survive.")
        self.assertNotIn("de_a", remaining["Shared"]["maps"])
        self.assertIn("de_b", remaining["Shared"]["maps"])

    # ------------------------------------------------------------------
    # delete_map_files
    # ------------------------------------------------------------------

    def test_delete_map_files_rejects_empty_name(self) -> None:
        with self.assertRaises(DeleteServiceError):
            delete_service.delete_map_files("   ")

    def test_delete_map_files_rejects_still_referenced_map(self) -> None:
        self._write_master({"Normal": {"options": {}, "maps": {"de_referenced": {}}}})
        with self.assertRaises(DeleteServiceError):
            delete_service.delete_map_files("de_referenced")

    def test_delete_map_files_rejects_missing_files(self) -> None:
        self._write_master({"Normal": {"options": {}, "maps": {}}})
        with self.assertRaises(DeleteServiceError):
            delete_service.delete_map_files("de_never_installed")

    def test_delete_map_files_deletes_bsp_and_bz2(self) -> None:
        self._write_master({"Normal": {"options": {}, "maps": {}}})
        bsp = constants.MAPS_FOLDER / "de_orphan.bsp"
        bz2 = constants.MAPS_FOLDER / "de_orphan.bsp.bz2"
        bsp.write_bytes(b"fake bsp")
        bz2.write_bytes(b"fake compressed")

        result = delete_service.delete_map_files("de_orphan")

        self.assertEqual(result["status"], "success")
        self.assertCountEqual(result["deleted_files"], ["de_orphan.bsp", "de_orphan.bsp.bz2"])
        self.assertFalse(bsp.exists())
        self.assertFalse(bz2.exists())


if __name__ == "__main__":
    unittest.main()
