#!/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
from unittest import mock


SCRIPT_DIR = Path(__file__).resolve().parent
BUILDER = SCRIPT_DIR / "build-engine-agent-full-grant-migration-artifact.mjs"
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
PATCH_ID = "engine-agent-full-grant-migration-20990101-001"
STORE_REL = "nodedc-source/server/engineAgents/store.js"
PREDECESSOR_SHA256 = "52daa43499d6d9a97fe7ffa891edb9212b7791e733f91dd3ca686d42739b7e9a"
TARGET_SHA256 = "2e62654c2dc12905efcc83a9dff45a818dd7b47924a10600160835c4416540e9"


def load_runner():
    loader = importlib.machinery.SourceFileLoader(
        "nodedc_agent_grant_migration_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 EngineAgentFullGrantMigrationArtifactTest(unittest.TestCase):
    def build(self, artifact_dir, patch_id=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_exact_reproducible_and_runner_accepted(self):
        with tempfile.TemporaryDirectory(prefix="nodedc-agent-grant-migration-") as directory:
            root = Path(directory)
            first_dir = root / "first"
            second_dir = root / "second"
            first_dir.mkdir()
            second_dir.mkdir()
            first = self.build(first_dir)
            second = self.build(second_dir)
            first_artifact = Path(first["artifact"])
            second_artifact = Path(second["artifact"])

            self.assertEqual(first["entries"], [STORE_REL])
            self.assertEqual(first["predecessorSha256"], PREDECESSOR_SHA256)
            self.assertEqual(first["targetSha256"], TARGET_SHA256)
            self.assertEqual(first["sha256"], hashlib.sha256(first_artifact.read_bytes()).hexdigest())
            self.assertEqual(first["sha256"], second["sha256"])
            self.assertEqual(first_artifact.read_bytes(), second_artifact.read_bytes())

            with tarfile.open(first_artifact, "r:gz") as archive:
                members = archive.getmembers()
                names = {member.name for member in members}
                manifest = archive.extractfile("manifest.env").read().decode("utf-8")
                files = archive.extractfile("files.txt").read().decode("utf-8")
                store = archive.extractfile(f"payload/{STORE_REL}").read()
            self.assertEqual(
                manifest,
                f"id={PATCH_ID}\ncomponent=engine\ntype=app-overlay\n",
            )
            self.assertEqual(files, f"{STORE_REL}\n")
            self.assertEqual(hashlib.sha256(store).hexdigest(), TARGET_SHA256)
            self.assertFalse(any(member.issym() or member.islnk() for member in members))
            self.assertFalse(any(
                name.startswith("payload/nodedc-source/server/data/")
                or name.startswith("payload/nodedc-source/dist/")
                or name.startswith("payload/nodedc-source/server/credentialSink/")
                for name in names
            ))

            with tempfile.TemporaryDirectory(prefix="nodedc-agent-grant-runner-") as work:
                manifest_value, entries, _payload = RUNNER.load_artifact(
                    first_artifact,
                    Path(work),
                )
            self.assertEqual(manifest_value["id"], PATCH_ID)
            self.assertEqual(tuple(entries), RUNNER.ENGINE_AGENT_FULL_GRANT_MIGRATION_ARTIFACT_ENTRIES)
            self.assertTrue(RUNNER.is_engine_agent_full_grant_migration_slice("engine", entries))
            self.assertEqual(RUNNER.component_services("engine", entries), ("nodedc-backend",))
            self.assertEqual(RUNNER.component_builds("engine", entries), ())
            self.assertEqual(
                RUNNER.component_healthchecks("engine", entries, ("nodedc-backend",)),
                (
                    "http://127.0.0.1:3001/health",
                    "http://127.0.0.1:3001/internal/engine-credential-sink/v1/health",
                ),
            )

    def test_predecessor_and_runtime_acceptance_are_exact(self):
        with tempfile.TemporaryDirectory(prefix="nodedc-agent-grant-predecessor-") as directory:
            root = Path(directory)
            store = root / STORE_REL
            store.parent.mkdir(parents=True)
            store.write_text("predecessor", encoding="utf-8")
            override = root / RUNNER.ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL
            override.parent.mkdir(parents=True)
            override.write_text(
                RUNNER.expected_engine_data_product_publish_grant_override(),
                encoding="utf-8",
            )
            original_root = RUNNER.COMPONENTS["engine"]["payload_root"]
            RUNNER.COMPONENTS["engine"]["payload_root"] = root
            try:
                with mock.patch.object(RUNNER, "sha256_file", return_value=PREDECESSOR_SHA256):
                    self.assertEqual(
                        RUNNER.preflight_engine_agent_full_grant_migration_predecessor(),
                        PREDECESSOR_SHA256,
                    )
                with mock.patch.object(RUNNER, "sha256_file", return_value="0" * 64):
                    with self.assertRaisesRegex(RUNNER.DeployError, "predecessor drift"):
                        RUNNER.preflight_engine_agent_full_grant_migration_predecessor()
            finally:
                RUNNER.COMPONENTS["engine"]["payload_root"] = original_root

        expected = f"store-v2-full-developer:{TARGET_SHA256}"
        with mock.patch.object(
            RUNNER,
            "engine_backend_container_id",
            return_value="container-id",
        ):
            with mock.patch.object(
                RUNNER,
                "run_engine_backend_probe",
                return_value=expected,
            ) as probe:
                self.assertEqual(RUNNER.accept_engine_agent_full_grant_migration_runtime(), expected)
        arguments, label = probe.call_args.args[:2]
        self.assertEqual(label, "agent full grant migration")
        self.assertEqual(arguments[0:2], ("node", "--input-type=module"))
        self.assertIn(TARGET_SHA256, arguments)
        self.assertEqual(probe.call_args.kwargs["container_id"], "container-id")

    def test_builder_rejects_invalid_id_and_overwrite(self):
        with tempfile.TemporaryDirectory(prefix="nodedc-agent-grant-negative-") as directory:
            root = Path(directory)
            environment = {**os.environ, "NODEDC_DEPLOY_ARTIFACT_DIR": str(root)}
            invalid = subprocess.run(
                ["node", str(BUILDER), "engine-data-product-publish-grant-20990101-001"],
                check=False,
                capture_output=True,
                text=True,
                env=environment,
            )
            self.assertNotEqual(invalid.returncode, 0)
            self.assertIn("fresh-patch-id", invalid.stderr)

            issued = subprocess.run(
                ["node", str(BUILDER), "engine-agent-full-grant-migration-20260717-001"],
                check=False,
                capture_output=True,
                text=True,
                env=environment,
            )
            self.assertNotEqual(issued.returncode, 0)
            self.assertIn("patch_id_already_issued", issued.stderr)

            self.build(root)
            duplicate = subprocess.run(
                ["node", str(BUILDER), PATCH_ID],
                check=False,
                capture_output=True,
                text=True,
                env=environment,
            )
            self.assertNotEqual(duplicate.returncode, 0)
            self.assertIn("artifact_already_exists", duplicate.stderr)


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