"""Deterministic parser for the approved booking-room Markdown fixture."""

from __future__ import annotations

import hashlib
import json
import re
from dataclasses import dataclass
from pathlib import Path


PROCESSOR_NAME = "booking-md-fixture-importer"
PROCESSOR_VERSION = "1.0.0"
RESULT_SCHEMA_VERSION = "1.0"

_WORKSHEET_RE = re.compile(r"^## 工作表：(.*\S)\s*$")
_ROW_RE = re.compile(
    r"^\|\s*第\s*(\d+)\s*行\s*"
    r"\|\s*([^|]+?)\s*"
    r"\|\s*([^|]+?)\s*"
    r"\|\s*(\d+)\s*\|\s*$"
)
_SOURCE_FILE_RE = re.compile(r"^来源文件：`([^`]+)`\s*$")
_MAPPING_FILE_RE = re.compile(r"^房型映射文件：`([^`]+)`\s*$")


@dataclass(frozen=True)
class FixtureRow:
    worksheet: str
    row_no: int
    group_code_raw: str
    type_of_room_raw: str
    no_of_rooms: int

    @property
    def group_code_key(self) -> str:
        return self.group_code_raw.strip().upper()

    def canonical_source(self) -> dict[str, object]:
        return {
            "worksheet": self.worksheet,
            "row_no": self.row_no,
            "group_code_raw": self.group_code_raw,
            "group_code_key": self.group_code_key,
            "type_of_room_raw": self.type_of_room_raw,
            "no_of_rooms": self.no_of_rooms,
        }

    def sha256(self) -> str:
        return hashlib.sha256(
            canonical_json_bytes(self.canonical_source())
        ).hexdigest()


@dataclass(frozen=True)
class FixtureDocument:
    source_file: str
    mapping_file: str
    rows: tuple[FixtureRow, ...]

    @property
    def worksheet_count(self) -> int:
        return len({row.worksheet for row in self.rows})

    @property
    def distinct_group_code_count(self) -> int:
        return len({row.group_code_key for row in self.rows})


def canonical_json_bytes(value: object) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")


def parse_fixture_markdown(text: str) -> FixtureDocument:
    source_file = ""
    mapping_file = ""
    worksheet: str | None = None
    rows: list[FixtureRow] = []
    coordinates: set[tuple[str, int]] = set()

    for physical_line_no, raw_line in enumerate(text.splitlines(), start=1):
        line = raw_line.strip()
        source_match = _SOURCE_FILE_RE.fullmatch(line)
        if source_match:
            source_file = source_match.group(1).strip()
            continue
        mapping_match = _MAPPING_FILE_RE.fullmatch(line)
        if mapping_match:
            mapping_file = mapping_match.group(1).strip()
            continue
        worksheet_match = _WORKSHEET_RE.fullmatch(line)
        if worksheet_match:
            worksheet = worksheet_match.group(1).strip()
            if not worksheet:
                raise ValueError(
                    f"empty worksheet at Markdown line {physical_line_no}"
                )
            continue
        row_match = _ROW_RE.fullmatch(line)
        if not row_match:
            continue
        if worksheet is None:
            raise ValueError(
                f"data row before worksheet at Markdown line {physical_line_no}"
            )
        row = FixtureRow(
            worksheet=worksheet,
            row_no=int(row_match.group(1)),
            group_code_raw=row_match.group(2).strip(),
            type_of_room_raw=row_match.group(3).strip(),
            no_of_rooms=int(row_match.group(4)),
        )
        if not row.group_code_key:
            raise ValueError(
                f"empty Group Code at Markdown line {physical_line_no}"
            )
        if not row.type_of_room_raw:
            raise ValueError(
                f"empty type of room at Markdown line {physical_line_no}"
            )
        if row.no_of_rooms <= 0:
            raise ValueError(
                f"invalid room quantity at Markdown line {physical_line_no}"
            )
        coordinate = (row.worksheet, row.row_no)
        if coordinate in coordinates:
            raise ValueError(
                "duplicate worksheet/row coordinate "
                f"{row.worksheet!r}/{row.row_no}"
            )
        coordinates.add(coordinate)
        rows.append(row)

    if not source_file:
        raise ValueError("fixture source file metadata is missing")
    if not mapping_file:
        raise ValueError("fixture mapping file metadata is missing")
    if not rows:
        raise ValueError("fixture contains no data rows")
    return FixtureDocument(
        source_file=source_file,
        mapping_file=mapping_file,
        rows=tuple(rows),
    )


def parse_fixture_file(path: Path) -> FixtureDocument:
    return parse_fixture_markdown(path.read_text(encoding="utf-8"))


def rule_set_sha256(contract_path: Path) -> str:
    digest = hashlib.sha256()
    digest.update(contract_path.read_bytes())
    digest.update(b"\0")
    digest.update(Path(__file__).read_bytes())
    return digest.hexdigest()


def build_fixture_parse_result(
    row: FixtureRow,
    *,
    rule_sha256: str,
) -> dict[str, object]:
    source_sha256 = row.sha256()
    return {
        "result_schema_version": RESULT_SCHEMA_VERSION,
        "status": "accepted",
        "source_row": {
            "worksheet": row.worksheet,
            "row_no": row.row_no,
            "sha256": source_sha256,
        },
        "processor_version": PROCESSOR_VERSION,
        "rule_set_sha256": rule_sha256,
        "group_code_raw": row.group_code_raw,
        "group_code_key": row.group_code_key,
        "type_of_room_raw": row.type_of_room_raw,
        "no_of_rooms": row.no_of_rooms,
        "room_items": [
            {
                "item_no": 1,
                "room_type_raw": row.type_of_room_raw,
                "room_type_code": row.type_of_room_raw,
                "quantity": row.no_of_rooms,
                "unit_price": None,
                "currency_code": None,
                "price_token_raw": None,
                "source_fragment": row.type_of_room_raw,
            }
        ],
        "warnings": [],
        "errors": [],
    }
