import unittest
from src.climax.__main__ import (
    MaXProjectConfig,
    dir_is_max,
    _sync_bundles,
    _choose_between,
)
from pathlib import Path
import tempfile
import subprocess
import typer
from unittest.mock import patch
import types
import sys


TEST_CONFIG_FILE = "tests_assets/max_config.xml"
TEST_CONFIG_EMPTY_FILE = "tests_assets/empty_max_config.xml"
TEST_CONFIG_FILE_BAD_ROOT = "tests_assets/bad_root_max_config.xml"
TEST_MAX_PROJECT_DIR = "tests_assets/max_project_dir"


class TestConfigFiles(unittest.TestCase):
    def setUp(self):
        pass

    def test_config(self):
        config = MaXProjectConfig(TEST_CONFIG_FILE)
        self.assertEqual(Path(TEST_CONFIG_FILE).resolve(), config.config_file_path)
        self.assertEqual(["fr", "en"], config.languages)
        self.assertEqual("mon Corpus Numérique", config.title)
        self.assertEqual("dev", config.env)
        self.assertEqual("max-tei-bundle", config.vocabulary_bundle)
        config.title = "new test title"
        self.assertEqual("new test title", config.title)

    def test_empty_config(self):
        config = MaXProjectConfig(TEST_CONFIG_EMPTY_FILE)
        config.title = "empty test title"
        self.assertEqual("empty test title", config.title)

    def test_bad_config(self):
        with self.assertRaises(ValueError) as context:  # noqa: F841
            MaXProjectConfig(TEST_CONFIG_FILE_BAD_ROOT)

    def test_set_languages(self):
        config = MaXProjectConfig("nothere")
        config.languages = ["fr", "en", "es", "jp"]
        self.assertEqual(["fr", "en", "es", "jp"], config.languages)

    def test_dir_is_max_project(self):
        self.assertTrue(dir_is_max(Path(TEST_MAX_PROJECT_DIR)))
        self.assertFalse(dir_is_max(Path(".")))

    def test_max_version(self):
        config = MaXProjectConfig("nothere")
        config.version = {"name": "name", "url": "url"}
        self.assertEqual({"name": "name", "url": "url"}, config.version)

    def test_max_project(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            p = subprocess.run(["climax", "--help"], stdout=subprocess.PIPE)
            self.assertTrue("--show-completion" in p.stdout.decode("utf-8"))
            p = subprocess.run(["climax", "new", tmpdir], stdout=subprocess.PIPE)
            self.assertTrue(Path(tmpdir, "config.xml").is_file())
            self.assertTrue(Path(tmpdir, ".max").is_dir())
            p = subprocess.run(["climax", "projects"], stdout=subprocess.PIPE)
            self.assertTrue(tmpdir in p.stdout.decode("utf-8"))
            # p = subprocess.run(
            #    ["climax", "info", "--directory", tmpdir], stdout=subprocess.PIPE
            # )
            # print(p.stdout.decode("utf-8"))

    def test_bundles_add_remove(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            max_test_dir = Path(tmpdir).resolve()

            # make new max instance and test default bundles
            subprocess.run(["climax", "new", tmpdir], stdout=subprocess.PIPE)
            self.assertTrue(
                Path(max_test_dir, ".max/basex/webapp/max/bundles/max-dev").exists()
            )
            self.assertTrue(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-dumb-xml"
                ).exists()
            )
            self.assertTrue(
                Path(max_test_dir, ".max/basex/webapp/max/bundles/max-export").exists()
            )

            # remove all bundles
            subprocess.run(
                ["climax", "bundles-remove", "max-dev", "--directory", max_test_dir],
                stdout=subprocess.PIPE,
            )
            self.assertTrue(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-dev/.ignore"
                ).exists()
            )
            subprocess.run(
                [
                    "climax",
                    "bundles-remove",
                    "max-dumb-xml",
                    "--directory",
                    max_test_dir,
                ],
                stdout=subprocess.PIPE,
            )
            self.assertTrue(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-dumb-xml/.ignore"
                ).exists()
            )
            subprocess.run(
                ["climax", "bundles-remove", "max-export", "--directory", max_test_dir],
                stdout=subprocess.PIPE,
            )
            self.assertTrue(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-export/.ignore"
                ).exists()
            )

            # add previously removed bundles
            subprocess.run(
                ["climax", "bundles-add", "max-dev", "--directory", max_test_dir],
                stdout=subprocess.PIPE,
            )
            self.assertFalse(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-dev/.ignore"
                ).exists()
            )
            subprocess.run(
                ["climax", "bundles-add", "max-dumb-xml", "--directory", max_test_dir],
                stdout=subprocess.PIPE,
            )
            self.assertFalse(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-dumb-xml/.ignore"
                ).exists()
            )
            subprocess.run(
                ["climax", "bundles-add", "max-export", "--directory", max_test_dir],
                stdout=subprocess.PIPE,
            )
            self.assertFalse(
                Path(
                    max_test_dir, ".max/basex/webapp/max/bundles/max-export/.ignore"
                ).exists()
            )

    def test_sync_bundle_install_failure_does_not_mark_installed(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            root_dir = Path(tmpdir).resolve()
            bundles_dir = Path(root_dir, ".max", "basex", "webapp", "max", "bundles")
            failing_bundle_dir = Path(bundles_dir, "failing-bundle")
            failing_bundle_dir.mkdir(parents=True)

            # Bundle has an install script, so _sync_bundles will attempt an install.
            Path(failing_bundle_dir, "install.xq").write_text("()", encoding="utf-8")

            config = MaXProjectConfig(Path(root_dir, "config.xml"))
            config.bundles = {"failing-bundle": {"name": "failing-bundle", "url": ""}}
            config.write()

            with (
                patch("src.climax.__main__.subprocess.run") as run_mock,
                patch("src.climax.__main__._ping_projects"),
            ):
                run_mock.return_value.returncode = 2
                with self.assertRaises(typer.Exit) as context:
                    _sync_bundles(root_dir)

            self.assertEqual(2, context.exception.exit_code)
            self.assertFalse(Path(failing_bundle_dir, ".installed").exists())

    def test_choose_between_cancel_raises_keyboardinterrupt(self):
        fake_module = types.SimpleNamespace()

        class FakeTerminalMenu:
            def __init__(self, values, title=""):  # noqa: ARG002
                pass

            def show(self):
                return None

        fake_module.TerminalMenu = FakeTerminalMenu

        with patch.dict(sys.modules, {"simple_term_menu": fake_module}):
            with self.assertRaises(KeyboardInterrupt):
                _choose_between(["a"], "pick one")


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