import doctest
import importlib
import json
import sys
import tempfile
import unittest
from pathlib import Path

import ocyan.core.fender
from configtype.jsonconfig import JsonFinder, setup_search_paths
from ocyan.core.plugins import get_plugins
import ocyan.core.utils.appextension
import ocyan.core.utils.cache
import ocyan.core.utils.csp
import ocyan.core.utils.currency
import ocyan.core.utils.dirtyhacks
import ocyan.core.utils.fixture
import ocyan.core.utils.itertools
import ocyan.core.utils.rudiments
import ocyan.core.utils.theme
import ocyan.core.utils.typeconversion


class PluginDiscoveryTests(unittest.TestCase):
    def test_discovers_eager_and_lazy_configs_from_namespace_packages(self):
        """Plugin discovery supports both eager and lazy leaf-package exports."""
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            eager = root / "ocyan" / "plugin" / "test_eager_plugin"
            lazy = root / "ocyan" / "plugin" / "test_lazy_plugin"
            eager.mkdir(parents=True)
            lazy.mkdir(parents=True)

            # No shared ocyan/__init__.py or ocyan/plugin/__init__.py is created:
            # these fixture packages exercise the PEP 420 namespace contract.
            (eager / "__init__.py").write_text(
                "from ocyan.core.plugins import OcyanPluginConfig\n"
                "class EagerPluginConfig(OcyanPluginConfig):\n"
                "    pass\n"
            )
            (lazy / "entrypoint.py").write_text(
                "from ocyan.core.plugins import OcyanPluginConfig\n"
                "class LazyPluginConfig(OcyanPluginConfig):\n"
                "    pass\n"
            )
            (lazy / "__init__.py").write_text(
                "__all__ = ['LazyPluginConfig']\n"
                "def __getattr__(name):\n"
                "    if name == 'LazyPluginConfig':\n"
                "        from .entrypoint import LazyPluginConfig\n"
                "        return LazyPluginConfig\n"
                "    raise AttributeError(name)\n"
            )

            module_names = [
                "ocyan.plugin.test_eager_plugin",
                "ocyan.plugin.test_lazy_plugin",
                "ocyan.plugin.test_lazy_plugin.entrypoint",
            ]
            sys.path.insert(0, temp_dir)
            try:
                importlib.invalidate_caches()
                for name in module_names:
                    sys.modules.pop(name, None)

                lazy_module = importlib.import_module("ocyan.plugin.test_lazy_plugin")
                self.assertEqual(
                    lazy_module.LazyPluginConfig.__name__, "LazyPluginConfig"
                )

                discovered = {
                    type(plugin).__module__ + "." + type(plugin).__name__
                    for plugin in get_plugins()
                }
                self.assertIn(
                    "ocyan.plugin.test_eager_plugin.EagerPluginConfig", discovered
                )
                self.assertIn(
                    "ocyan.plugin.test_lazy_plugin.entrypoint.LazyPluginConfig",
                    discovered,
                )
            finally:
                sys.path.remove(temp_dir)
                for name in module_names:
                    sys.modules.pop(name, None)


class FenderConfigTests(unittest.TestCase):
    def setUp(self):
        self.original_meta_path = list(sys.meta_path)

    def tearDown(self):
        sys.meta_path[:] = self.original_meta_path
        sys.modules.pop("ocyan.core.json", None)
        importlib.reload(ocyan.core.fender)

    def _reload_fender(self):
        sys.modules.pop("ocyan.core.json", None)
        return importlib.reload(ocyan.core.fender)

    def test_import_without_config_uses_defaults(self):
        sys.meta_path[:] = [
            finder for finder in sys.meta_path if not isinstance(finder, JsonFinder)
        ]
        fender = self._reload_fender()

        self.assertEqual(fender.config.settings, {})
        self.assertEqual(fender.config.ocyan_plugins, [])
        self.assertTrue(fender.config.is_webshop)

    def test_configured_json_values_override_defaults(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            config_path = Path(temp_dir) / "ocyan.json"
            config_path.write_text(
                json.dumps(
                    {
                        "settings": {"django": {"name": "Core test project"}},
                        "ocyan_plugins": ["barebone", "ocyan.plugin.django"],
                    }
                )
            )
            setup_search_paths(temp_dir)
            fender = self._reload_fender()

            self.assertEqual(fender.config.get("django", "name"), "Core test project")
            self.assertEqual(
                fender.config.ocyan_plugins, ["barebone", "ocyan.plugin.django"]
            )
            self.assertFalse(fender.config.is_webshop)

    def test_malformed_json_configuration_raises(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            (Path(temp_dir) / "ocyan.json").write_text("{not valid JSON")
            setup_search_paths(temp_dir)

            with self.assertRaises(json.JSONDecodeError):
                self._reload_fender()


def load_tests(loader, tests, ignore):  # pylint: disable=W0613
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.appextension))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.cache))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.csp))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.currency))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.dirtyhacks))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.fixture))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.itertools))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.theme))
    tests.addTests(doctest.DocTestSuite(ocyan.core.utils.typeconversion))
    return tests
