#!/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="审批运控待提交参数包，并生成车间电脑侧参数交接包。")
    parser.add_argument("pending_commit", help="运控待提交参数包 pending_control_commit.yaml。")
    parser.add_argument("--operator-id", required=True, help="审批操作员 ID。")
    parser.add_argument("--approval-note", default="", help="审批备注。")
    parser.add_argument("--output", default="", help="交接包输出路径；为空时写到会话目录 control/approved_control_parameter_handoff.yaml。")
    parser.add_argument("--update-dataset-index", action=argparse.BooleanOptionalAction, default=True, help="是否把交接包路径写回 dataset_index.yaml。")
    parser.add_argument("--dry-run", action="store_true", help="只校验并打印结果，不修改文件。")
    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_pending_commit(package: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]]:
    if int(package.get("schema_version", 0) or 0) != 1:
        raise ValueError("pending_control_commit.schema_version 必须为 1。")
    session = require_map(package.get("session"), "pending_control_commit.session")
    source = require_map(package.get("source"), "pending_control_commit.source")
    approval = require_map(package.get("approval"), "pending_control_commit.approval")
    commit_request = require_map(package.get("commit_request"), "pending_control_commit.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")
    require_string(commit_request.get("parameter_version"), "commit_request.parameter_version")
    require_string(commit_request.get("chassis_type"), "commit_request.chassis_type")
    require_string(commit_request.get("control_axis"), "commit_request.control_axis")
    require_string(commit_request.get("controller_algorithm"), "commit_request.controller_algorithm")
    require_string(commit_request.get("control_role"), "commit_request.control_role")
    require_map(commit_request.get("estimated_params"), "commit_request.estimated_params")
    return session, source, approval, commit_request


def validate_estimated_params_digest(session: dict[str, Any], source: dict[str, Any]) -> dict[str, str]:
    digest = require_map(source.get("estimated_params_digest"), "source.estimated_params_digest")
    checksum_type = require_string(digest.get("checksum_type"), "estimated_params_digest.checksum_type")
    checksum_value = require_string(digest.get("checksum_value"), "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}")
    actual_checksum = sha256_file(estimated_params_path)
    if actual_checksum != checksum_value:
        raise ValueError("运控算法输出参数摘要不一致，禁止审批交接。")
    return {
        "checksum_type": checksum_type,
        "checksum_value": checksum_value,
        "estimated_params_file": relative_to_root(estimated_params_path, dataset_root),
    }


def resolve_output_path(args: argparse.Namespace, session: dict[str, Any]) -> Path:
    if args.output:
        return Path(args.output).expanduser().resolve(strict=False)
    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    return (dataset_root / "control" / "approved_control_parameter_handoff.yaml").resolve(strict=False)


def build_handoff_package(
    pending_path: Path,
    output_path: Path,
    package: dict[str, Any],
    args: argparse.Namespace,
) -> dict[str, Any]:
    session, source, approval, commit_request = validate_pending_commit(package)
    digest = validate_estimated_params_digest(session, source)
    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    approved_us = now_us()

    approval["required"] = bool_value(approval.get("required", True))
    approval["approved"] = True
    approval["operator_id"] = args.operator_id
    approval["approved_timestamp_us"] = approved_us
    approval["approval_note"] = args.approval_note
    package["commit_state"] = "approved_waiting_vehicle_handoff"
    package["vehicle_handoff"] = {
        "handoff_file": relative_to_root(output_path, dataset_root),
        "handoff_state": "ready_for_vehicle_adapter",
        "generated_timestamp_us": approved_us,
        "generated_by": "approve_control_pending_parameters.py",
    }

    return {
        "schema_version": 1,
        "handoff_state": "ready_for_vehicle_adapter",
        "created_timestamp_us": approved_us,
        "session": {
            "session_id": session["session_id"],
            "site_id": session.get("site_id", ""),
            "vehicle_id": session["vehicle_id"],
            "dataset_root": session["dataset_root"],
        },
        "source": {
            "pending_commit_file": relative_to_root(pending_path, dataset_root),
            "dataset_index_file": source.get("dataset_index_file", ""),
            "estimated_params_file": digest["estimated_params_file"],
            "estimated_params_digest": {
                "checksum_type": digest["checksum_type"],
                "checksum_value": digest["checksum_value"],
            },
        },
        "approval": {
            "approved": True,
            "operator_id": args.operator_id,
            "approved_timestamp_us": approved_us,
            "approval_note": args.approval_note,
        },
        "vehicle_commit_request": {
            "parameter_version": commit_request["parameter_version"],
            "chassis_type": commit_request["chassis_type"],
            "control_axis": commit_request["control_axis"],
            "controller_algorithm": commit_request["controller_algorithm"],
            "control_role": commit_request["control_role"],
            "commit_reason": commit_request.get("commit_reason", ""),
            "persistent_write": bool_value(commit_request.get("persistent_write", True)),
            "estimated_params": commit_request["estimated_params"],
        },
        "vehicle_adapter_contract": {
            "status": "waiting_vehicle_side_adapter",
            "required_request": "vehicle_side_control_parameter_write_adapter",
            "required_digest_check": "sha256",
            "rollback_required_on_vehicle_commit_failure": bool_value(
                package.get("rollback", {}).get("rollback_required_on_vehicle_commit_failure", True)
            ),
        },
    }


def update_dataset_index(
    pending_package: dict[str, Any],
    handoff_path: Path,
    handoff_package: dict[str, Any],
) -> None:
    session = require_map(pending_package.get("session"), "pending_control_commit.session")
    source = require_map(pending_package.get("source"), "pending_control_commit.source")
    dataset_root = Path(require_string(session.get("dataset_root"), "session.dataset_root")).expanduser()
    dataset_index_file = source.get("dataset_index_file", "")
    if not isinstance(dataset_index_file, str) or not dataset_index_file:
        return
    dataset_index_path = resolve_dataset_file(dataset_index_file, dataset_root)
    if not dataset_index_path.exists():
        return

    index = load_yaml(dataset_index_path)
    metadata = index.setdefault("metadata", {})
    metadata["control_parameter_handoff_file"] = relative_to_root(handoff_path, dataset_root)
    metadata["control_parameter_handoff_state"] = handoff_package["handoff_state"]
    metadata["control_pending_commit_state"] = pending_package.get("commit_state", "")
    metadata["control_pending_commit_approved"] = True
    metadata["control_pending_commit_approved_operator_id"] = handoff_package["approval"]["operator_id"]
    metadata["control_pending_commit_approved_timestamp_us"] = handoff_package["approval"]["approved_timestamp_us"]
    write_yaml(dataset_index_path, index)


def main() -> int:
    args = parse_args()
    pending_path = Path(args.pending_commit).expanduser().resolve(strict=False)
    package = load_yaml(pending_path)
    session, _, _, commit_request = validate_pending_commit(package)
    output_path = resolve_output_path(args, session)
    handoff_package = build_handoff_package(pending_path, output_path, package, args)

    print(f"[OK] 运控待提交参数包校验通过：{pending_path}")
    print(f"[OK] 参数版本：{commit_request['parameter_version']}")
    print(f"[OK] 目标车辆：{session['vehicle_id']}")
    print(f"[OK] 交接包状态：{handoff_package['handoff_state']}")

    if args.dry_run:
        print("[OK] dry-run：未修改文件。")
        return 0

    write_yaml(output_path, handoff_package)
    write_yaml(pending_path, package)
    if args.update_dataset_index:
        update_dataset_index(package, output_path, handoff_package)
    print(f"[OK] 已生成运控参数交接包：{output_path}")
    return 0


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