from __future__ import annotations

import hashlib
import json
import os
import shutil
from collections.abc import Callable
from contextlib import suppress
from datetime import UTC, datetime
from pathlib import Path
from uuid import uuid4

from k1link.device_plugins.xgrids_k1.calibration_schema import (
    parse_k1_factory_calibration,
)
from k1link.device_plugins.xgrids_k1.protocol.application_execution import (
    ApplicationAuthorityLoader,
)
from k1link.device_plugins.xgrids_k1.protocol.calibration_file import (
    FACTORY_CAMERA_CALIBRATION_PATH,
    FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH,
    CalibrationFileContent,
)
from k1link.device_plugins.xgrids_k1.protocol.calibration_mqtt import (
    FactoryCalibrationReadResult,
    ReviewedCalibrationMqttReader,
)

DEVICE_CALIBRATION_SCHEMA_VERSION = "missioncore.device-calibration/v1alpha2"
CALIBRATION_SNAPSHOT_MANIFEST_VERSION = "missioncore.k1-calibration-snapshot/v1"
CALIBRATION_CAPTURE_SUFFIX = "k1_factory_calibration"

CalibrationTransportFactory = Callable[[str, bool], ReviewedCalibrationMqttReader]


class DeviceCalibrationSnapshotError(RuntimeError):
    """A live factory-calibration snapshot could not be sealed safely."""


class DeviceCalibrationSnapshotReader:
    """Read two exact K1 files and seal an append-only private snapshot."""

    def __init__(
        self,
        authority_loader: ApplicationAuthorityLoader,
        *,
        compatibility_profile_id: str,
        transport_factory: CalibrationTransportFactory | None = None,
    ) -> None:
        self._authority_loader = authority_loader
        self._compatibility_profile_id = compatibility_profile_id
        self._transport_factory = transport_factory or _default_transport_factory

    def capture(
        self,
        *,
        host: str,
        evidence_root: Path,
        allow_device_ap: bool,
    ) -> dict[str, object]:
        authority = self._authority_loader.load()
        transport = self._transport_factory(host, allow_device_ap)
        result = transport.read_factory_calibration(authority)
        return seal_factory_calibration_snapshot(
            result,
            evidence_root=evidence_root,
            compatibility_profile_id=self._compatibility_profile_id,
        )


def unavailable_device_calibration_snapshot(
    compatibility_profile_id: str | None,
) -> dict[str, object]:
    return {
        "schema_version": DEVICE_CALIBRATION_SCHEMA_VERSION,
        "status": "unavailable",
        "compatibility_profile_id": compatibility_profile_id,
        "device_internal_calibration": None,
        "vehicle_extrinsics": "host-domain-not-owned-by-plugin",
        "reason_code": "device-calibration-not-observed",
    }


def seal_factory_calibration_snapshot(
    result: FactoryCalibrationReadResult,
    *,
    evidence_root: Path,
    compatibility_profile_id: str,
    captured_at: datetime | None = None,
) -> dict[str, object]:
    files_by_path = {item.path: item for item in result.files}
    if set(files_by_path) != {
        FACTORY_CAMERA_CALIBRATION_PATH,
        FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH,
    }:
        raise DeviceCalibrationSnapshotError(
            "factory calibration result does not contain the exact two-file set"
        )
    normalized_calibration = parse_k1_factory_calibration(
        files_by_path[FACTORY_CAMERA_CALIBRATION_PATH].content,
        files_by_path[FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH].content,
    ).normalized_profile()

    observed_at = (captured_at or datetime.now(UTC)).astimezone(UTC)
    captured_at_utc = observed_at.isoformat(timespec="milliseconds").replace("+00:00", "Z")
    snapshot_id = uuid4().hex
    content_identity = hashlib.sha256()
    content_identity.update(result.binding.vendor_device_id.encode("ascii"))
    content_identity.update(b"\x00")
    content_identity.update(result.binding.device_serial.encode("ascii"))
    for path in sorted(files_by_path):
        content_identity.update(b"\x00")
        content_identity.update(path.encode("utf-8"))
        content_identity.update(bytes.fromhex(files_by_path[path].content_sha256))

    artifact_specs = (
        (
            FACTORY_CAMERA_CALIBRATION_PATH,
            "camera.yaml",
            files_by_path[FACTORY_CAMERA_CALIBRATION_PATH],
        ),
        (
            FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH,
            "extrinsic_camera_lidar.yaml",
            files_by_path[FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH],
        ),
    )
    artifact_documents = [
        _artifact_document(source_path, artifact_name, content)
        for source_path, artifact_name, content in artifact_specs
    ]
    private_root = evidence_root.expanduser().resolve() / "private" / "device-calibration"
    _ensure_private_directory(private_root)
    stamp = observed_at.strftime("%Y%m%dT%H%M%SZ")
    final_name = f"{stamp}_{CALIBRATION_CAPTURE_SUFFIX}_{snapshot_id[:12]}"
    final_dir = private_root / final_name
    staging_dir = private_root / f".{final_name}.incomplete"
    try:
        staging_dir.mkdir(mode=0o700, exist_ok=False)
        for _source_path, artifact_name, content in artifact_specs:
            _write_exclusive(staging_dir / artifact_name, content.content)
        private_reference = str(final_dir)
        manifest = {
            "schema_version": CALIBRATION_SNAPSHOT_MANIFEST_VERSION,
            "snapshot_id": snapshot_id,
            "captured_at_utc": captured_at_utc,
            "compatibility_profile_id": compatibility_profile_id,
            "content_identity_sha256": content_identity.hexdigest(),
            "device": {
                "vendor_device_id": result.binding.vendor_device_id,
                "device_serial": result.binding.device_serial,
                "device_model": result.binding.device_model,
                "platform_type": result.binding.device_type,
                "software_version": result.binding.software_version,
                "system_version": result.binding.system_version,
                "is_activated": result.binding.is_activated,
            },
            "source": {
                "transport": "mqtt-protobuf-read-only",
                "request_command": 5,
                "write_command_available": False,
                "automatic_retry": False,
                "path_allowlist": [
                    FACTORY_CAMERA_CALIBRATION_PATH,
                    FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH,
                ],
                "transport_snapshot": result.transport,
            },
            "artifacts": artifact_documents,
            "normalized_calibration": normalized_calibration,
            "storage": {
                "classification": "private-device-calibration",
                "append_only": True,
                "snapshot_path": private_reference,
            },
        }
        manifest_bytes = (
            json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
        ).encode("utf-8")
        _write_exclusive(staging_dir / "manifest.json", manifest_bytes)
        os.rename(staging_dir, final_dir)
        _fsync_directory(private_root)
    except Exception:
        with suppress(OSError):
            shutil.rmtree(staging_dir)
        raise

    return {
        "schema_version": DEVICE_CALIBRATION_SCHEMA_VERSION,
        "status": "available",
        "compatibility_profile_id": compatibility_profile_id,
        "snapshot_id": snapshot_id,
        "captured_at_utc": captured_at_utc,
        "device_internal_calibration": {
            "source": "xgrids-factory-data-live-read",
            "device_serial": result.binding.device_serial,
            "firmware_version": result.binding.software_version,
            "content_identity_sha256": content_identity.hexdigest(),
            "documents": artifact_documents,
            "private_snapshot_path": str(final_dir),
            "normalized_calibration": normalized_calibration,
            "camera_stream_mapping": normalized_calibration["mapping_proof"],
        },
        "vehicle_extrinsics": "host-domain-not-owned-by-plugin",
        "reason_code": None,
    }


def _default_transport_factory(host: str, allow_device_ap: bool) -> ReviewedCalibrationMqttReader:
    return ReviewedCalibrationMqttReader(host, allow_device_ap=allow_device_ap)


def _artifact_document(
    source_path: str,
    artifact_name: str,
    content: CalibrationFileContent,
) -> dict[str, object]:
    return {
        "source_path": source_path,
        "artifact_name": artifact_name,
        "sha256": content.content_sha256,
        "bytes": content.content_bytes,
        "encoding": "utf-8",
    }


def _ensure_private_directory(path: Path) -> None:
    path.mkdir(mode=0o700, parents=True, exist_ok=True)
    current = path
    while current.name in {"private", "device-calibration"}:
        with suppress(OSError):
            current.chmod(0o700)
        current = current.parent


def _write_exclusive(path: Path, payload: bytes) -> None:
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
    if hasattr(os, "O_NOFOLLOW"):
        flags |= os.O_NOFOLLOW
    descriptor = os.open(path, flags, 0o600)
    try:
        with os.fdopen(descriptor, "wb", closefd=False) as stream:
            stream.write(payload)
            stream.flush()
            os.fsync(stream.fileno())
    finally:
        os.close(descriptor)


def _fsync_directory(path: Path) -> None:
    flags = os.O_RDONLY
    if hasattr(os, "O_DIRECTORY"):
        flags |= os.O_DIRECTORY
    descriptor = os.open(path, flags)
    try:
        os.fsync(descriptor)
    finally:
        os.close(descriptor)
