"""End-to-end tests for the dependency-free EQL spell CLI."""

from __future__ import annotations

import json
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
CLI = ROOT / "scripts" / "eql-spells"
FIXTURE = ROOT / "tests" / "fixtures" / "eql-spells"


class EqlSpellsCliTests(unittest.TestCase):
    def setUp(self) -> None:
        self.temporary = tempfile.TemporaryDirectory()
        self.work = Path(self.temporary.name)
        self.game = self.work / "game"
        shutil.copytree(FIXTURE, self.game)
        self.db = self.work / "cache" / "spells.sqlite"

    def tearDown(self) -> None:
        self.temporary.cleanup()

    def run_cli(self, *arguments: str) -> subprocess.CompletedProcess[str]:
        return subprocess.run(
            [sys.executable, str(CLI), "--game-path", str(self.game), "--db", str(self.db), *arguments],
            cwd=ROOT,
            text=True,
            capture_output=True,
            check=False,
        )

    def json_stdout(self, *arguments: str) -> dict:
        result = self.run_cli(*arguments)
        self.assertEqual(result.returncode, 0, result.stderr)
        return json.loads(result.stdout)

    def test_build_and_exact_lookup_by_id_and_name(self) -> None:
        built = self.json_stdout("build")
        self.assertEqual(built["built"], {"descriptions": 3, "messages": 3, "spells": 3})
        self.assertTrue(self.db.is_file())

        by_id = self.json_stdout("get", "1")
        self.assertEqual(by_id["spell"]["name"], "Fire Bolt")
        self.assertEqual(by_id["spell"]["description"], "Launches a bolt of fire at your target.")
        self.assertEqual(by_id["spell"]["messages"]["caster_me"], "You cast Fire Bolt.")
        self.assertIn("fingerprint", by_id["source"])

        by_name = self.json_stdout("get", "water breathing")
        self.assertEqual(by_name["spell"]["id"], 2)

    def test_find_search_and_raw_output(self) -> None:
        found = self.json_stdout("find", "fire")
        self.assertEqual([spell["name"] for spell in found["results"]], ["Fire Bolt", "Fireball"])

        searched = self.json_stdout("search", "breathing underwater")
        self.assertEqual([spell["id"] for spell in searched["results"]], [2])

        raw = self.json_stdout("get", "1", "--raw")
        self.assertEqual(raw["spell"]["raw"]["line"], "1^Fire Bolt^0^alpha")
        self.assertEqual(raw["spell"]["raw"]["fields"], ["1", "Fire Bolt", "0", "alpha"])

    def test_query_automatically_rebuilds_after_source_change(self) -> None:
        self.json_stdout("build")
        descriptions = self.game / "dbstr_us.txt"
        with descriptions.open("a", encoding="utf-8") as handle:
            handle.write("2^6^Lets your target breathe underwater indefinitely.^0^\n")

        result = self.run_cli("search", "indefinitely")
        self.assertEqual(result.returncode, 1)
        self.assertIn("Duplicate spell description", json.loads(result.stderr)["error"]["message"])

        # Replace the existing description rather than adding a duplicate.
        descriptions.write_text(
            "1^6^Launches a bolt of fire at your target.^0^\n"
            "2^6^Lets your target breathe underwater indefinitely.^0^\n"
            "3^6^Launches a ball of fire at your target.^0^\n"
            "1^5^Fire^0^\n",
            encoding="utf-8",
        )
        rebuilt = self.json_stdout("search", "indefinitely")
        self.assertTrue(rebuilt["index_rebuilt"])
        self.assertEqual(rebuilt["results"][0]["id"], 2)

    def test_malformed_data_and_invalid_query_return_structured_errors(self) -> None:
        (self.game / "spells_us.txt").write_text("bad row\n", encoding="utf-8")
        malformed = self.run_cli("build")
        self.assertEqual(malformed.returncode, 1)
        self.assertEqual(json.loads(malformed.stderr)["error"]["code"], "data")

        shutil.copy2(FIXTURE / "spells_us.txt", self.game / "spells_us.txt")
        invalid_limit = self.run_cli("find", "fire", "--limit", "101")
        self.assertEqual(invalid_limit.returncode, 2)
        self.assertEqual(json.loads(invalid_limit.stderr)["error"]["code"], "usage")

        missing = self.run_cli("get", "999")
        self.assertEqual(missing.returncode, 1)
        self.assertEqual(json.loads(missing.stderr)["error"]["code"], "data")


if __name__ == "__main__":
    unittest.main()
