#
# Basic tests for a collection of osbuild modules.
#

import json
import os
import pathlib
import sys
import tempfile
import unittest

import osbuild
import osbuild.meta
from osbuild.monitor import NullMonitor
from osbuild.objectstore import ObjectStore
from osbuild.pipeline import Manifest, detect_host_runner
from .. import test


class TestDescriptions(unittest.TestCase):

    @unittest.skipUnless(test.TestBase.can_bind_mount(), "root-only")
    def test_stage_run(self):
        index = osbuild.meta.Index(os.curdir)
        info = index.get_module_info("Stage", "org.osbuild.noop")
        stage = osbuild.Stage(info, {}, None, None, {})

        with tempfile.TemporaryDirectory() as tmpdir:

            data = pathlib.Path(tmpdir, "data")
            storedir = pathlib.Path(tmpdir, "store")
            root = pathlib.Path("/")
            runner = detect_host_runner()
            monitor = NullMonitor(sys.stderr.fileno())
            libdir = os.path.abspath(os.curdir)
            store = ObjectStore(storedir)
            data.mkdir()

            res = stage.run(data, runner, root, store, monitor, libdir)

        self.assertEqual(res.success, True)
        self.assertEqual(res.id, stage.id)

    def test_manifest(self):
        index = osbuild.meta.Index(os.curdir)

        info = index.get_module_info("Stage", "org.osbuild.noop")

        manifest = Manifest()

        # each pipeline gets a noop stage with different
        # options so that their ids are different
        build = manifest.add_pipeline("build", None, None)
        build.add_stage(info, {"option": 1})

        tree = manifest.add_pipeline("tree",
                                     "org.osbuild.linux",
                                     build.id)
        tree.add_stage(info, {"option": 2})

        assembler = manifest.add_pipeline("assembler",
                                          "org.osbuild.inux",
                                          build.id)
        assembler.add_stage(info, {"option": 3})

        self.assertEqual(len(manifest.pipelines), 3)

        self.assertIn("build", manifest.pipelines)
        self.assertIn("tree", manifest.pipelines)
        self.assertIn("assembler", manifest.pipelines)

        self.assertIn("build", manifest)
        self.assertIn("tree", manifest)
        self.assertIn("assembler", manifest)

        # make sure the order is correct
        lst = ["build", "tree", "assembler"]
        for a, b in zip(manifest.pipelines, lst):
            self.assertEqual(a, b)

        for a, b in zip(manifest, [build, tree, assembler]):
            self.assertEqual(a.name, b.name)

        # check we get exceptions on unknown names
        with self.assertRaises(KeyError):
            _ = manifest.pipelines["foo"]

        with self.assertRaises(KeyError):
            _ = manifest["foo"]

        # check helper functions
        #  access by name
        check = manifest["build"]
        self.assertEqual(build.name, check.name)

        check = manifest["tree"]
        self.assertEqual(tree.name, check.name)

        check = manifest["assembler"]
        self.assertEqual(assembler.name, check.name)

        #  `None` return for unknown items
        check = manifest.get("foo")
        self.assertIsNone(check)

        #  id based access
        for i in [build, tree, assembler]:
            check = manifest[i.id]
            self.assertEqual(i.name, check.name)

    def check_moduleinfo(self, version):
        index = osbuild.meta.Index(os.curdir)

        modules = []
        for klass in ("Assembler", "Input", "Source", "Stage"):
            mods = index.list_modules_for_class(klass)
            modules += [(klass, module) for module in mods]

        self.assertTrue(modules)

        for module in modules:
            klass, name = module
            try:
                info = osbuild.meta.ModuleInfo.load(os.curdir, klass, name)
                schema = osbuild.meta.Schema(info.get_schema(version), name)
                res = schema.check()
                if not res:
                    err = "SCHEMA: " + json.dumps(schema.data, indent=2) + "\n"
                    err += "\n  ".join(str(e) for e in res)
                    self.fail(str(res) + "\n  " + err)
            except json.decoder.JSONDecodeError as e:
                msg = f"{klass} '{name}' has invalid STAGE_OPTS\n\t" + str(e)
                self.fail(msg)

    def test_moduleinfo(self):
        for version in ["1", "2"]:
            with self.subTest(version=version):
                self.check_moduleinfo(version)

    def test_schema(self):
        schema = osbuild.meta.Schema(None)
        self.assertFalse(schema)

        schema = osbuild.meta.Schema({"type": "bool"})  # should be 'boolean'
        self.assertFalse(schema.check().valid)
        self.assertFalse(schema)

        schema = osbuild.meta.Schema({"type": "array", "minItems": 3})
        self.assertTrue(schema.check().valid)
        self.assertTrue(schema)

        res = schema.validate([1, 2])
        self.assertFalse(res)
        res = schema.validate([1, 2, 3])
        self.assertTrue(res)
