import hashlib
import importlib.machinery
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import tarfile
import tempfile
import unittest


SCRIPT_DIR = Path(__file__).resolve().parent
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
BUILDER_PATH = SCRIPT_DIR / "build-engine-l1-agent-init-concurrency-artifact.mjs"
ENGINE_ROOT = SCRIPT_DIR.parent.parent.parent / "NODEDC_ENGINE_INFRA"
PATCH_ID = "engine-l1-agent-init-concurrency-20991231-999"
SOURCE_COMMIT = "c4077fe0d8d824fa58616eeb986dc78ca43070da"


def load_runner():
    loader = importlib.machinery.SourceFileLoader(
        "nodedc_engine_l1_agent_init_concurrency",
        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 EngineL1AgentInitConcurrencyArtifactTest(unittest.TestCase):
    def require_target_commit(self):
        current = subprocess.run(
            ["git", "-C", str(ENGINE_ROOT), "rev-parse", "HEAD"],
            check=True,
            capture_output=True,
            text=True,
        ).stdout.strip()
        if current != SOURCE_COMMIT:
            self.skipTest("historical Engine L1 agent-init source has advanced")

    def build(self, artifact_dir):
        self.require_target_commit()
        environment = os.environ.copy()
        environment["NODEDC_ENGINE_SOURCE_ROOT"] = str(ENGINE_ROOT)
        environment["NODEDC_DEPLOY_ARTIFACT_DIR"] = str(artifact_dir)
        completed = subprocess.run(
            ["node", str(BUILDER_PATH), PATCH_ID],
            check=True,
            capture_output=True,
            text=True,
            env=environment,
        )
        return json.loads(completed.stdout)

    def test_artifact_is_exact_deterministic_and_runner_accepted(self):
        with tempfile.TemporaryDirectory(
            prefix="nodedc-engine-l1-agent-init-",
        ) as directory:
            root = Path(directory)
            first = self.build(root / "first")
            second = self.build(root / "second")
            first_artifact = Path(first["artifact"])
            second_artifact = Path(second["artifact"])

            self.assertEqual(first["sourceCommit"], SOURCE_COMMIT)
            self.assertEqual(first_artifact.read_bytes(), second_artifact.read_bytes())
            self.assertEqual(
                first["sha256"],
                hashlib.sha256(first_artifact.read_bytes()).hexdigest(),
            )
            self.assertFalse(first["n8nCoreChanged"])

            manifest, entries, payload = RUNNER.load_artifact(
                first_artifact,
                root / "loaded",
            )
            self.assertEqual(manifest["component"], "engine")
            self.assertEqual(manifest["type"], "app-overlay")
            self.assertEqual(tuple(entries), tuple(first["entries"]))
            self.assertEqual(
                RUNNER.component_services("engine", entries),
                ("nodedc-backend", "app"),
            )
            self.assertEqual(
                RUNNER.component_healthchecks(
                    "engine",
                    entries,
                    ("nodedc-backend", "app"),
                ),
                (
                    "http://127.0.0.1:8080/",
                    "http://127.0.0.1:3001/health",
                ),
            )
            self.assertTrue(RUNNER.component_publish_dist("engine", entries))
            self.assertEqual(RUNNER.component_builds("engine", entries), ())

            payload_files = {
                path.relative_to(payload).as_posix()
                for path in payload.rglob("*")
                if path.is_file()
            }
            self.assertEqual(payload_files, set(first["targetSha256"]))
            for relative_path, expected in first["targetSha256"].items():
                self.assertEqual(
                    hashlib.sha256((payload / relative_path).read_bytes()).hexdigest(),
                    expected,
                )
            self.assertTrue(all("/server/data/" not in path for path in payload_files))
            self.assertTrue(all("/tests/" not in path for path in payload_files))
            self.assertTrue(all(not path.endswith(".env") for path in payload_files))
            self.assertTrue(all("n8n-data" not in path for path in payload_files))

            with tarfile.open(first_artifact, "r:gz") as archive:
                for member in archive.getmembers():
                    self.assertIn(member.type, (tarfile.REGTYPE, tarfile.DIRTYPE))
                    self.assertEqual(member.uid, 0)
                    self.assertEqual(member.gid, 0)
                    self.assertEqual(member.mtime, 0)
                    self.assertNotIn(".DS_Store", member.name)
                    self.assertNotIn("__MACOSX", member.name)


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