"""Tests for the import surface: an id is a module, `help` is the load."""
from __future__ import annotations

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

import klbr.skills
from klbr_runtime import skill_index

BODY = "# alpha\n\nOnce you have a workspace you can find code.\n"


class SkillsImportTests(unittest.TestCase):
    """Resolution is lazy, so a test installs first and imports after."""

    def setUp(self):
        self.temporary = tempfile.TemporaryDirectory(prefix="klbr-skill-import-")
        self.directory = Path(self.temporary.name)
        self.body_file = self.directory / "SKILL.md"
        self.body_file.write_text(BODY, encoding="utf-8")
        self.installed: list[str] = []

    def tearDown(self):
        self.temporary.cleanup()
        skill_index.install(self.directory / "absent.json")
        # The import system caches what it resolved; a real worker resolves once per
        # process, so clearing here only keeps one test's id from answering another's.
        for skill_id in self.installed:
            sys.modules.pop(f"klbr.skills.{skill_id}", None)

    def install(self, skill_id: str, *, refused=()):
        self.installed.append(skill_id)
        document = {
            "revision": "rev-import",
            "skills": [
                {
                    "id": skill_id,
                    "origin": "builtin",
                    "path": str(self.body_file),
                    "digest": "c" * 64,
                    "text": BODY,
                }
            ],
            "refused": [{"id": entry_id, "reason": reason} for entry_id, reason in refused],
        }
        path = self.directory / "skills.json"
        path.write_text(json.dumps(document), encoding="utf-8")
        skill_index.install(path)

    def test_an_id_is_an_attribute_and_its_documentation_is_the_body(self):
        self.install("alpha")
        self.assertIn("alpha", dir(klbr.skills))
        self.assertEqual(klbr.skills.index, ("alpha",))
        self.assertEqual(klbr.skills.revision, "rev-import")
        self.assertEqual(klbr.skills.alpha.__doc__, BODY)
        self.assertEqual(klbr.skills.alpha.id, "alpha")
        self.assertEqual(klbr.skills.alpha.digest, "c" * 64)
        self.assertEqual(klbr.skills.alpha.__file__, str(self.body_file))
        self.assertIn("Once you have a workspace", inspect.getdoc(klbr.skills.alpha))

    def test_importing_by_name_and_from_import_reach_the_same_module(self):
        self.install("alpha")
        imported = importlib.import_module("klbr.skills.alpha")
        self.assertIs(imported, klbr.skills.alpha)
        from klbr.skills import alpha  # noqa: F401 - the import is the assertion

        self.assertIs(alpha, klbr.skills.alpha)
        self.assertEqual(inspect.getsource(imported), BODY)

    def test_a_refused_id_names_the_reason_however_it_is_asked_for(self):
        self.install("alpha", refused=[("Beta", "skill id must be lower-case")])
        self.assertEqual(klbr.skills.refused[0].id, "Beta")
        with self.assertRaises(AttributeError) as attribute:
            klbr.skills.Beta
        self.assertIn("is not offered: skill id must be lower-case", str(attribute.exception))
        with self.assertRaises(ModuleNotFoundError) as imported:
            importlib.import_module("klbr.skills.Beta")
        self.assertIn("is not offered: skill id must be lower-case", str(imported.exception))

    def test_an_unknown_id_lists_what_is_available(self):
        self.install("alpha")
        with self.assertRaises(AttributeError) as raised:
            klbr.skills.nope
        self.assertIn("no skill 'nope'; available: alpha", str(raised.exception))

    def test_a_generation_without_a_payload_says_so_instead_of_listing_nothing(self):
        with self.assertRaises(AttributeError) as raised:
            klbr.skills.alpha
        self.assertIn("spawned with no skill set", str(raised.exception))
        self.assertEqual(klbr.skills.revision, "no-skills")


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