#!/usr/bin/env python3
"""把已审批传感器参数交接包落成车间电脑侧车辆参数配置。"""

from __future__ import annotations

import argparse
import hashlib
import sys
import time
from pathlib import Path
from typing import Any

try:
    import yaml
except ImportError as exc:
    raise SystemExit("缺少 PyYAML，请先在当前 Python 环境安装 yaml 模块。") from exc


def now_us() -> int:
    return int(time.time() * 1_000_000)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="消费 approved_sensor_parameter_handoff.yaml，生成车间电脑侧车辆传感器参数配置。"
    )
    parser.add_argument("handoff", help="已审批传感器参数交接包。")
    parser.add_argument("--output-dir", default="", help="配置输出目录；为空时写到 dataset_root/sensor/applied_parameters。")
    parser.add_argument("--dataset-index", default="", help="可选 dataset_index.yaml；为空时从 handoff.source.dataset_index_file 推断。")
    parser.add_argument("--operator-id", default="", help="执行本次落盘的操作员 ID。")
    parser.add_argument("--apply-note", default="", help="落盘备注。")
    parser.add_argument("--dry-run", action="store_true", help="只校验并打印将要写入的文件，不实际写入。")
    parser.add_argument("--update-dataset-index", action=argparse.BooleanOptionalAction, default=True, help="是否把落盘结果写回 dataset_index metadata。")
    return parser.parse_args()


def load_yaml(path: Path) -> dict[str, Any]:
    with path.open("r", encoding="utf-8") as stream:
        data = yaml.safe_load(stream) or {}
    if not isinstance(data, dict):
        raise ValueError(f"{path} 顶层必须是 YAML map。")
    return data


def write_yaml(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(yaml.safe_dump(data, sort_keys=False, allow_unicode=True), encoding="utf-8")


def require_map(value: Any, field_name: str) -> dict[str, Any]:
    if not isinstance(value, dict):
        raise ValueError(f"{field_name} 必须是 YAML map。")
    return value


def require_string(value: Any, field_name: str) -> str:
    if not isinstance(value, str) or not value.strip():
        raise ValueError(f"{field_name} 必须是非空字符串。")
    return value.strip()


def bool_value(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        return value.strip().lower() in ("1", "true", "yes", "y")
    return bool(value)


def relative_to_root(path: Path, root: Path) -> str:
    try:
        return str(path.resolve(strict=False).relative_to(root.resolve(strict=False)))
    except ValueError:
        return str(path.resolve(strict=False))


def resolve_dataset_file(path_value: str, dataset_root: Path) -> Path:
    path = Path(path_value).expanduser()
    if path.is_absolute():
        return path
    return (dataset_root / path).resolve(strict=False)


def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def validate_handoff(handoff: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]]:
    if int(handoff.get("schema_version", 0) or 0) != 1:
        raise ValueError("approved_sensor_parameter_handoff.schema_version 必须为 1。")
    if handoff.get("handoff_state") != "ready_for_vehicle_adapter":
        raise ValueError("handoff_state 必须为 ready_for_vehicle_adapter。")
    session = require_map(handoff.get("session"), "handoff.session")
    source = require_map(handoff.get("source"), "handoff.source")
    approval = require_map(handoff.get("approval"), "handoff.approval")
    vehicle_request = require_map(handoff.get("vehicle_commit_request"), "handoff.vehicle_commit_request")
    require_string(session.get("session_id"), "session.session_id")
    require_string(session.get("vehicle_id"), "session.vehicle_id")
    require_string(session.get("dataset_root"), "session.dataset_root")
    if not bool_value(approval.get("approved", False)):
        raise ValueError("handoff.approval.approved 必须为 true。")
    require_string(vehicle_request.get("parameter_version"), "vehicle_commit_request.parameter_version")
    require_string(vehicle_request.get("selected_task"), "vehicle_commit_request.selected_task")
    require_string(vehicle_request.get("task_subtype"), "vehicle_commit_request.task_subtype")
    require_string(vehicle_request.get("sensor_id"), "vehicle_commit_request.sensor_id")
    require_map(vehicle_request.get("estimated_params"), "vehicle_commit_request.estimated_params")
    return session, source, approval, vehicle_request


def validate_source_digest(session: dict[str, Any], source: dict[str, Any]) -> None:
    digest = require_map(source.get("estimated_params_digest"), "source.estimated_params_digest")
    checksum_type = require_string(digest.get("checksum_type"), "source.estimated_params_digest.checksum_type")
    checksum_value = require_string(digest.get("checksum_value"), "source.estimated_params_digest.checksum_value")
    if checksum_type.lower() != "sha256":
        raise ValueError("当前只支持 sha256 摘要校验。")
    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    estimated_params_file = require_string(source.get("estimated_params_file"), "source.estimated_params_file")
    estimated_params_path = resolve_dataset_file(estimated_params_file, dataset_root)
    if not estimated_params_path.exists():
        raise FileNotFoundError(f"算法输出参数文件不存在：{estimated_params_path}")
    if sha256_file(estimated_params_path) != checksum_value:
        raise ValueError("算法输出参数摘要不一致，禁止生成车辆参数配置。")


def resolve_output_dir(args: argparse.Namespace, dataset_root: Path) -> Path:
    if args.output_dir:
        return Path(args.output_dir).expanduser().resolve(strict=False)
    return (dataset_root / "sensor" / "applied_parameters").resolve(strict=False)


def build_vehicle_parameter_config(
    handoff_path: Path,
    output_file: Path,
    session: dict[str, Any],
    source: dict[str, Any],
    approval: dict[str, Any],
    vehicle_request: dict[str, Any],
    args: argparse.Namespace,
) -> dict[str, Any]:
    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    created_us = now_us()
    return {
        "schema_version": 1,
        "config_state": "ready_for_vehicle_side_import",
        "created_timestamp_us": created_us,
        "session": {
            "session_id": session["session_id"],
            "site_id": session.get("site_id", ""),
            "vehicle_id": session["vehicle_id"],
            "dataset_root": session["dataset_root"],
        },
        "source": {
            "approved_handoff_file": relative_to_root(handoff_path, dataset_root),
            "pending_commit_file": source.get("pending_commit_file", ""),
            "dataset_index_file": source.get("dataset_index_file", ""),
            "estimated_params_file": source.get("estimated_params_file", ""),
            "estimated_params_digest": source.get("estimated_params_digest", {}),
        },
        "approval": {
            "approved": True,
            "operator_id": approval.get("operator_id", ""),
            "approved_timestamp_us": approval.get("approved_timestamp_us", 0),
            "approval_note": approval.get("approval_note", ""),
        },
        "apply": {
            "operator_id": args.operator_id,
            "apply_note": args.apply_note,
            "persistent_write": bool_value(vehicle_request.get("persistent_write", True)),
            "config_file": relative_to_root(output_file, dataset_root),
        },
        "sensor_parameter": {
            "parameter_version": vehicle_request["parameter_version"],
            "selected_task": vehicle_request["selected_task"],
            "task_subtype": vehicle_request["task_subtype"],
            "sensor_id": vehicle_request["sensor_id"],
            "estimated_params": vehicle_request["estimated_params"],
        },
        "vehicle_import_contract": {
            "status": "waiting_vehicle_side_import",
            "required_request": "vehicle_side_sensor_parameter_write_adapter",
            "required_digest_check": "sha256",
        },
    }


def build_receipt(
    output_file: Path,
    session: dict[str, Any],
    vehicle_request: dict[str, Any],
    args: argparse.Namespace,
) -> dict[str, Any]:
    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    return {
        "schema_version": 1,
        "apply_state": "workshop_config_exported",
        "created_timestamp_us": now_us(),
        "session": {
            "session_id": session["session_id"],
            "site_id": session.get("site_id", ""),
            "vehicle_id": session["vehicle_id"],
            "dataset_root": session["dataset_root"],
        },
        "sensor_id": vehicle_request["sensor_id"],
        "parameter_version": vehicle_request["parameter_version"],
        "selected_task": vehicle_request["selected_task"],
        "task_subtype": vehicle_request["task_subtype"],
        "operator_id": args.operator_id,
        "apply_note": args.apply_note,
        "vehicle_parameter_config_file": relative_to_root(output_file, dataset_root),
        "vehicle_parameter_config_digest": {
            "checksum_type": "sha256",
            "checksum_value": sha256_file(output_file),
        },
    }


def resolve_dataset_index_path(args: argparse.Namespace, dataset_root: Path, source: dict[str, Any]) -> Path | None:
    if args.dataset_index:
        return Path(args.dataset_index).expanduser().resolve(strict=False)
    dataset_index_file = source.get("dataset_index_file", "")
    if not isinstance(dataset_index_file, str) or not dataset_index_file:
        return None
    return resolve_dataset_file(dataset_index_file, dataset_root)


def update_dataset_index(
    dataset_index_path: Path | None,
    dataset_root: Path,
    output_file: Path,
    receipt_file: Path,
    receipt: dict[str, Any],
) -> None:
    if dataset_index_path is None or not dataset_index_path.exists():
        return
    index = load_yaml(dataset_index_path)
    metadata = index.setdefault("metadata", {})
    metadata["sensor_parameter_vehicle_config_file"] = relative_to_root(output_file, dataset_root)
    metadata["sensor_parameter_apply_receipt_file"] = relative_to_root(receipt_file, dataset_root)
    metadata["sensor_parameter_apply_state"] = receipt["apply_state"]
    metadata["sensor_parameter_apply_version"] = receipt["parameter_version"]
    metadata["sensor_parameter_apply_sensor_id"] = receipt["sensor_id"]
    metadata["sensor_parameter_apply_checksum_type"] = receipt["vehicle_parameter_config_digest"]["checksum_type"]
    metadata["sensor_parameter_apply_checksum_value"] = receipt["vehicle_parameter_config_digest"]["checksum_value"]
    write_yaml(dataset_index_path, index)


def main() -> int:
    args = parse_args()
    handoff_path = Path(args.handoff).expanduser().resolve(strict=False)
    handoff = load_yaml(handoff_path)
    session, source, approval, vehicle_request = validate_handoff(handoff)
    validate_source_digest(session, source)

    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    output_dir = resolve_output_dir(args, dataset_root)
    sensor_id = require_string(vehicle_request.get("sensor_id"), "vehicle_commit_request.sensor_id")
    parameter_version = require_string(
        vehicle_request.get("parameter_version"),
        "vehicle_commit_request.parameter_version",
    )
    output_file = output_dir / f"{sensor_id}_{parameter_version}.yaml"
    receipt_file = output_dir / "sensor_parameter_apply_receipt.yaml"
    config = build_vehicle_parameter_config(
        handoff_path,
        output_file,
        session,
        source,
        approval,
        vehicle_request,
        args,
    )

    print(f"[OK] 已审批传感器参数交接包校验通过：{handoff_path}")
    print(f"[OK] 目标传感器：{sensor_id}")
    print(f"[OK] 参数版本：{parameter_version}")
    print(f"[OK] 将生成车辆侧导入配置：{output_file}")

    if args.dry_run:
        print("[OK] dry-run 模式，不写入文件。")
        return 0

    write_yaml(output_file, config)
    receipt = build_receipt(output_file, session, vehicle_request, args)
    write_yaml(receipt_file, receipt)
    if args.update_dataset_index:
        dataset_index_path = resolve_dataset_index_path(args, dataset_root, source)
        update_dataset_index(dataset_index_path, dataset_root, output_file, receipt_file, receipt)
    print(f"[OK] 已生成车辆侧导入配置：{output_file}")
    print(f"[OK] 已生成车间电脑侧落盘回执：{receipt_file}")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as exc:
        print(f"[错误] {exc}", file=sys.stderr)
        raise SystemExit(1)
