# -*- coding: utf-8 -*-
"""
check、parse、build API 及边界测试（C1–C3, P1–P3, B1–B2）。
"""

import tempfile
import yaml
from pathlib import Path

import pytest

from pb_calibration import check, parse, build
from pb_calibration._proto_io import parse_pb_text


def _valid_pb_path():
    p = Path(__file__).resolve().parent / "vehicle_config.pb.txt"
    if not p.exists():
        pytest.skip(f"测试输入不存在: {p}")
    return str(p)


# ----- check() -----


def test_check_valid_pb():
    """C1: 合法 pb 返回 (True, None)。"""
    ok, err = check(_valid_pb_path())
    assert ok is True
    assert err is None


def test_check_invalid_content():
    """C2: 非法/损坏内容返回 (False, 非空错误信息)。"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".pb.txt", delete=False) as f:
        f.write("not a valid protobuf text\nvehicle_info { broken\n")
        bad_path = f.name
    try:
        ok, err = check(bad_path)
        assert ok is False
        assert err is not None and len(err) > 0
    finally:
        Path(bad_path).unlink(missing_ok=True)


def test_check_file_not_found():
    """C3: 文件不存在时 check 返回 (False, 错误信息)。"""
    ok, err = check("/nonexistent/path/vehicle_config.pb.txt")
    assert ok is False
    assert err is not None and len(err) > 0


# ----- parse() -----


def test_parse_valid_structure():
    """P1: 合法 pb 解析成功，返回 dict 含四类数据，yaml_dir 下结构正确。S1: order_manifest 含预期 key。"""
    pb_path = _valid_pb_path()
    with tempfile.TemporaryDirectory(prefix="pb_cal_parse_") as tmpdir:
        data, report = parse(pb_path, tmpdir)
        assert "vehicle_info" in data
        assert "vehicle_param" in data
        assert "extrinsics" in data
        assert "intrinsics" in data

        root = Path(tmpdir)
        assert (root / "order_manifest.yaml").exists()
        assert (root / "vehicle_info.yaml").exists()
        assert (root / "vehicle_param.yaml").exists()
        assert (root / "extrinsics").is_dir()
        assert (root / "intrinsics").is_dir()

        with open(root / "order_manifest.yaml", "r", encoding="utf-8") as f:
            manifest = yaml.safe_load(f) or {}
        for key in ("vehicle_info_file", "vehicle_param_file", "extrinsics", "intrinsics"):
            assert key in manifest, f"order_manifest 缺少 key: {key}"

        n_ext = len(data.get("extrinsics") or [])
        n_int = len(data.get("intrinsics") or [])
        assert len(list((root / "extrinsics").iterdir())) == n_ext
        assert len(list((root / "intrinsics").iterdir())) == n_int


def test_parse_invalid_raises():
    """P2: 非法 pb 解析抛异常。"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".pb.txt", delete=False) as f:
        f.write("vehicle_info { model: 123 }\n")  # model 应为 string，或截断导致解析失败
        bad_path = f.name
    try:
        with tempfile.TemporaryDirectory(prefix="pb_cal_parse_") as tmpdir:
            with pytest.raises(Exception):
                parse(bad_path, tmpdir)
    finally:
        Path(bad_path).unlink(missing_ok=True)


def test_parse_then_build_consumable():
    """P3: 解析得到的 yaml_dir 可被 build 消费，输出可被 parse_pb_text 解析。"""
    pb_path = _valid_pb_path()
    with tempfile.TemporaryDirectory(prefix="pb_cal_p3_") as tmpdir:
        parse(pb_path, tmpdir)
        out_pb = Path(tmpdir) / "out.pb.txt"
        build(tmpdir, str(out_pb))
        assert out_pb.exists()
        with open(out_pb, "r", encoding="utf-8") as f:
            parse_pb_text(f.read())


# ----- build() -----


def test_build_valid_succeeds():
    """B1: 合法 yaml_dir 组装成功，输出存在且可解析。"""
    pb_path = _valid_pb_path()
    with tempfile.TemporaryDirectory(prefix="pb_cal_b1_") as tmpdir:
        parse(pb_path, tmpdir)
        out_pb = Path(tmpdir) / "rebuilt.pb.txt"
        build(tmpdir, str(out_pb))
        assert out_pb.exists()
        with open(out_pb, "r", encoding="utf-8") as f:
            parse_pb_text(f.read())


def test_build_missing_manifest_raises():
    """B2: 缺少 order_manifest.yaml 时 build 抛异常。"""
    with tempfile.TemporaryDirectory(prefix="pb_cal_b2_") as tmpdir:
        out_pb = Path(tmpdir) / "out.pb.txt"
        with pytest.raises(FileNotFoundError):
            build(tmpdir, str(out_pb))


# ----- 边界（S3 在 test_roundtrip，S4 如下） -----


def test_pb_with_colon_extra_spaces_parses():
    """S4: 冒号后多空格的 pb 文本仍可被 check/parse 正确解析。"""
    # 最小合法 vehicle_config：冒号后双空格
    pb_text = '''vehicle_info {
  model:  "test_model"
  id:  "id1"
  vin:  ""
  plate:  ""
}
vehicle_param {
  length:  1.0
}
'''
    with tempfile.NamedTemporaryFile(mode="w", suffix=".pb.txt", delete=False) as f:
        f.write(pb_text)
        path = f.name
    try:
        ok, err = check(path)
        assert ok is True, f"check 应通过，err={err}"
        with tempfile.TemporaryDirectory(prefix="pb_cal_s4_") as tmpdir:
            data, _ = parse(path, tmpdir)
            assert data.get("vehicle_info", {}).get("model") == "test_model"
    finally:
        Path(path).unlink(missing_ok=True)
