#!/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-device-plane-b2-discovery-ingress-artifact.mjs"
)
RUNNER_PATH = SCRIPT_DIR / "nodedc-deploy"
COMPOSE = (
    SCRIPT_DIR.parent.parent
    / "device-plane/docker-compose.device-plane.yml"
)


def load_runner():
    loader = importlib.machinery.SourceFileLoader(
        "nodedc_device_plane_b2_ingress_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 DevicePlaneB2DiscoveryIngressArtifactTest(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_exact_deterministic_and_database_free(self):
        with tempfile.TemporaryDirectory(
            prefix="nodedc-device-plane-b2-ingress-",
        ) as directory:
            artifact_dir = Path(directory)
            patch_id = "device-plane-b2-discovery-ingress-unit-001"
            first = self.build(artifact_dir, patch_id)
            first_bytes = Path(first["artifact"]).read_bytes()
            second = self.build(artifact_dir, patch_id)
            second_bytes = Path(second["artifact"]).read_bytes()

            self.assertEqual(first_bytes, second_bytes)
            self.assertEqual(
                first["sha256"],
                hashlib.sha256(first_bytes).hexdigest(),
            )
            self.assertEqual(
                first["transition"],
                "verified-b2-discovery-only",
            )
            self.assertEqual(
                first["services"],
                ["device-control-core", "device-gateway"],
            )
            self.assertNotIn("device-postgres", first["services"])
            self.assertEqual(
                first["entries"],
                list(RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES),
            )

            with tarfile.open(first["artifact"], "r:gz") as archive:
                files = (
                    archive.extractfile("files.txt")
                    .read()
                    .decode("utf-8")
                    .splitlines()
                )
                compose = archive.extractfile(
                    "payload/docker-compose.device-plane.yml"
                ).read()
                descriptor = json.loads(
                    archive.extractfile(
                        "payload/deployment/"
                        "device-plane-b2-discovery-ingress-v1.json"
                    )
                    .read()
                    .decode("utf-8")
                )

            self.assertEqual(files, first["entries"])
            self.assertEqual(
                hashlib.sha256(compose).hexdigest(),
                RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_COMPOSE_SHA256,
            )
            self.assertEqual(
                descriptor,
                RUNNER.expected_device_plane_b2_discovery_ingress_descriptor(),
            )

    def test_compose_opens_only_discovery_tcp_and_preserves_database(self):
        compose = COMPOSE.read_text(encoding="utf-8")
        postgres, stateless = compose.split("  device-control-core:", 1)
        self.assertNotIn("9921", postgres)
        self.assertNotIn("device-plane-control", postgres)
        self.assertIn(
            'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
            stateless,
        )
        self.assertIn(
            'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "true"',
            stateless,
        )
        self.assertIn('"0.0.0.0:9921:9921"', stateless)
        self.assertNotIn("DEVICE_GATEWAY_COMMAND", compose)
        self.assertNotIn("POSTGRES_PASSWORD:", compose)

    def test_runner_builds_only_stateless_services_and_accepts_new_health(self):
        entries = RUNNER.DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES
        self.assertEqual(
            RUNNER.component_services("device-plane", entries),
            ("device-control-core", "device-gateway"),
        )
        builds = RUNNER.component_builds("device-plane", entries)
        self.assertEqual(len(builds), 2)
        checks = RUNNER.component_healthchecks(
            "device-plane",
            entries,
            ("device-control-core", "device-gateway"),
        )
        self.assertEqual(
            checks[0]["expected_json"]["discoveryIngest"],
            "enabled",
        )
        self.assertEqual(
            checks[1]["expected_json"]["publicIngress"],
            "discovery-only",
        )
        self.assertEqual(
            checks[1]["expected_json"]["commandTransport"],
            "disabled",
        )

    def test_runtime_acceptance_preserves_postgres_and_replaces_stateless(self):
        before = {
            "schemaVersion": "nodedc.device-plane.runtime-inventory.v1",
            "composeProject": "nodedc-device-plane",
            "services": [
                runtime_item("device-control-core", "1", "a"),
                runtime_item("device-gateway", "2", "b"),
                runtime_item("device-postgres", "3", "c"),
            ],
        }
        containers = {
            "core": stateless_container(
                service="device-control-core",
                container_id="4" * 64,
                image_id="sha256:" + "d" * 64,
                image=RUNNER.DEVICE_PLANE_CONTROL_CORE_IMAGE,
                ports={
                    "18120/tcp": [{
                        "HostIp": "127.0.0.1",
                        "HostPort": "18120",
                    }],
                },
                environment={
                    "DEVICE_DISCOVERY_INGEST_ENABLED": "true",
                    "DEVICE_GATEWAY_CORE_TOKEN_FILE":
                        "/run/nodedc-secrets/gateway-core-token",
                    "DEVICE_IDENTIFIER_PEPPER_FILE":
                        "/run/nodedc-secrets/identifier-pepper",
                },
                mounts=[
                    secret_mount(
                        RUNNER.DEVICE_PLANE_POSTGRES_PASSWORD_FILE,
                        "/run/nodedc-secrets/postgres-password",
                    ),
                    secret_mount(
                        RUNNER.DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE,
                        "/run/nodedc-secrets/gateway-core-token",
                    ),
                    secret_mount(
                        RUNNER.DEVICE_PLANE_IDENTIFIER_PEPPER_FILE,
                        "/run/nodedc-secrets/identifier-pepper",
                    ),
                ],
            ),
            "gateway": stateless_container(
                service="device-gateway",
                container_id="5" * 64,
                image_id="sha256:" + "e" * 64,
                image=RUNNER.DEVICE_PLANE_GATEWAY_IMAGE,
                ports={
                    "18121/tcp": [{
                        "HostIp": "127.0.0.1",
                        "HostPort": "18121",
                    }],
                    "9921/tcp": [{
                        "HostIp": "0.0.0.0",
                        "HostPort": "9921",
                    }],
                },
                environment={
                    "DEVICE_GATEWAY_LISTEN_ENABLED": "true",
                    "DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "true",
                    "DEVICE_GATEWAY_TCP_HOST": "0.0.0.0",
                    "DEVICE_GATEWAY_TCP_PORT": "9921",
                    "DEVICE_GATEWAY_CORE_URL":
                        "http://device-control-core:18120",
                    "DEVICE_GATEWAY_CORE_TOKEN_FILE":
                        "/run/nodedc-secrets/gateway-core-token",
                    "DEVICE_GATEWAY_CORE_TIMEOUT_MS": "5000",
                    "DEVICE_GATEWAY_MAX_BUFFERED_BYTES": "65536",
                    "DEVICE_GATEWAY_MAX_SESSIONS": "100",
                    "DEVICE_GATEWAY_MAX_SESSIONS_PER_ADDRESS": "10",
                    "DEVICE_GATEWAY_MAX_CONNECTIONS_PER_MINUTE_PER_ADDRESS":
                        "30",
                    "DEVICE_GATEWAY_SESSION_TIMEOUT_MS": "10000",
                },
                mounts=[
                    secret_mount(
                        RUNNER.DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE,
                        "/run/nodedc-secrets/gateway-core-token",
                    ),
                ],
            ),
            "postgres": postgres_container(),
        }
        service_ids = {
            "device-control-core": ("core",),
            "device-gateway": ("gateway",),
            "device-postgres": ("postgres",),
        }
        with (
            mock.patch.object(
                RUNNER,
                "validate_device_plane_runtime_secret_metadata",
            ),
            mock.patch.object(
                RUNNER,
                "device_plane_service_container_ids",
                side_effect=lambda service: service_ids[service],
            ),
            mock.patch.object(
                RUNNER,
                "inspect_device_plane_container",
                side_effect=lambda container_id: containers[container_id],
            ),
            mock.patch.object(
                RUNNER,
                "validate_device_plane_network_contract",
            ) as network,
            mock.patch.object(
                RUNNER,
                "assert_loopback_tcp_port_open",
            ) as port_open,
        ):
            accepted = (
                RUNNER.validate_device_plane_b2_discovery_ingress_runtime(
                    before
                )
            )
        self.assertEqual(
            accepted["device-postgres"]["containerId"],
            "3" * 64,
        )
        self.assertEqual(network.call_count, 2)
        port_open.assert_called_once_with(9921)


def runtime_item(service, container_digit, image_digit):
    return {
        "service": service,
        "containerId": container_digit * 64,
        "imageId": "sha256:" + image_digit * 64,
        "status": "running",
        "running": True,
        "health": "healthy",
        "restartCount": 0,
    }


def secret_mount(source, destination):
    return {
        "Type": "bind",
        "Source": str(source),
        "Destination": destination,
        "RW": False,
    }


def stateless_container(
    *,
    service,
    container_id,
    image_id,
    image,
    ports,
    environment,
    mounts,
):
    return {
        "Id": container_id,
        "Image": image_id,
        "RestartCount": 0,
        "State": {
            "Status": "running",
            "Running": True,
            "Restarting": False,
            "ExitCode": 0,
            "Error": "",
            "Health": {"Status": "healthy"},
        },
        "Config": {
            "Image": image,
            "User": "1000:1000",
            "Labels": {
                "com.docker.compose.project": "nodedc-device-plane",
                "com.docker.compose.service": service,
            },
            "Env": [f"{key}={value}" for key, value in environment.items()],
        },
        "HostConfig": {
            "PortBindings": ports,
            "RestartPolicy": {"Name": "unless-stopped"},
            "ReadonlyRootfs": True,
            "CapDrop": ["ALL"],
            "SecurityOpt": ["no-new-privileges:true"],
        },
        "NetworkSettings": {
            "Ports": ports,
            "Networks": {
                RUNNER.DEVICE_PLANE_PRIVATE_NETWORK: {},
                RUNNER.DEVICE_PLANE_CONTROL_NETWORK: {},
            },
        },
        "Mounts": mounts,
    }


def postgres_container():
    return {
        "Id": "3" * 64,
        "Image": "sha256:" + "c" * 64,
        "RestartCount": 0,
        "State": {
            "Status": "running",
            "Running": True,
            "Restarting": False,
            "ExitCode": 0,
            "Error": "",
            "Health": {"Status": "healthy"},
        },
        "Config": {
            "Image": "postgres:16-alpine",
            "User": "",
            "Labels": {
                "com.docker.compose.project": "nodedc-device-plane",
                "com.docker.compose.service": "device-postgres",
            },
            "Env": [],
        },
        "HostConfig": {
            "PortBindings": {},
            "RestartPolicy": {"Name": "unless-stopped"},
        },
        "NetworkSettings": {
            "Ports": {},
            "Networks": {
                RUNNER.DEVICE_PLANE_PRIVATE_NETWORK: {},
            },
        },
        "Mounts": [
            secret_mount(
                RUNNER.DEVICE_PLANE_POSTGRES_PASSWORD_FILE,
                "/run/nodedc-secrets/postgres-password",
            ),
            {
                "Type": "volume",
                "Name": RUNNER.DEVICE_PLANE_POSTGRES_VOLUME,
                "Destination": "/var/lib/postgresql/data",
                "RW": True,
            },
        ],
    }


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