#!/usr/bin/env python3
import hashlib
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path


SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = SCRIPT_DIR / "build-device-plane-artifact.mjs"
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
EXPECTED_ENTRIES = [
    ".dockerignore",
    "package.json",
    "package-lock.json",
    "docker-compose.device-plane.yml",
    "packages/device-protocol-contract",
    "packages/arusnavi-b2-adapter",
    "services/device-control-core",
    "services/device-gateway",
]


def load_runner():
    loader = importlib.machinery.SourceFileLoader(
        "nodedc_device_plane_artifact_runner_under_test",
        str(RUNNER_PATH),
    )
    spec = importlib.util.spec_from_loader(loader.name, loader)
    module = importlib.util.module_from_spec(spec)
    loader.exec_module(module)
    return module


RUNNER = load_runner()


class DevicePlaneArtifactTest(unittest.TestCase):
    def build(self, artifact_dir, patch_id):
        environment = os.environ.copy()
        environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
        result = subprocess.run(
            ["node", str(BUILDER), patch_id],
            check=True,
            capture_output=True,
            text=True,
            env=environment,
        )
        return json.loads(result.stdout)

    def test_artifact_is_narrow_safe_and_deterministic(self):
        with tempfile.TemporaryDirectory(
            prefix="nodedc-device-plane-artifact-",
        ) as directory:
            artifact_dir = Path(directory)
            first = self.build(
                artifact_dir,
                "device-plane-foundation-unit-001",
            )
            artifact = Path(first["artifact"])
            first_bytes = artifact.read_bytes()
            second = self.build(
                artifact_dir,
                "device-plane-foundation-unit-001",
            )
            second_bytes = Path(second["artifact"]).read_bytes()

            self.assertEqual(first["component"], "device-plane")
            self.assertEqual(first["entries"], EXPECTED_ENTRIES)
            self.assertEqual(
                first["services"],
                ["device-control-core", "device-gateway"],
            )
            self.assertEqual(
                first["sha256"],
                hashlib.sha256(first_bytes).hexdigest(),
            )
            self.assertEqual(first["sha256"], second["sha256"])
            self.assertEqual(first_bytes, second_bytes)

            with tarfile.open(artifact, "r:gz") as archive:
                members = archive.getmembers()
                names = {member.name for member in members}
                files = (
                    archive.extractfile("files.txt")
                    .read()
                    .decode("utf-8")
                    .splitlines()
                )
                manifest = (
                    archive.extractfile("manifest.env")
                    .read()
                    .decode("utf-8")
                )
                compose = (
                    archive.extractfile(
                        "payload/docker-compose.device-plane.yml",
                    )
                    .read()
                    .decode("utf-8")
                )
                regular_payloads = [
                    archive.extractfile(member).read()
                    for member in members
                    if member.isfile()
                ]

            self.assertEqual(files, EXPECTED_ENTRIES)
            self.assertEqual(
                manifest,
                "id=device-plane-foundation-unit-001\n"
                "component=device-plane\n"
                "type=app-overlay\n",
            )
            self.assertIn(
                "payload/services/device-control-core/Dockerfile",
                names,
            )
            self.assertIn(
                "payload/services/device-gateway/Dockerfile",
                names,
            )
            self.assertFalse(any(
                "/test/" in name
                or "/node_modules/" in name
                or Path(name).name.startswith(".env")
                or name.startswith("payload/docs/")
                or name.startswith("payload/runtime/")
                or name.startswith("payload/secrets/")
                for name in names
            ))
            self.assertNotIn("9921:9921", compose)
            self.assertIn('DEVICE_GATEWAY_LISTEN_ENABLED: "false"', compose)
            self.assertIn('DEVICE_DISCOVERY_INGEST_ENABLED: "false"', compose)
            self.assertNotIn(b"-----BEGIN PRIVATE KEY-----", b"\n".join(
                regular_payloads,
            ))

            with tempfile.TemporaryDirectory(
                prefix="nodedc-device-plane-runner-load-",
            ) as work_directory:
                manifest_loaded, entries_loaded, payload_loaded = (
                    RUNNER.load_artifact(artifact, Path(work_directory))
                )
            self.assertEqual(manifest_loaded["component"], "device-plane")
            self.assertEqual(entries_loaded, EXPECTED_ENTRIES)
            self.assertEqual(payload_loaded.name, "payload")


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