"""Pure deterministic rules for monthly channel report payloads."""

from __future__ import annotations

import calendar
import re
from collections import defaultdict
from datetime import date
from decimal import Decimal
from typing import Dict, Iterable, List, Sequence, Tuple

from monthly_reports.contracts import (
    KB_SHEET,
    STANDARD_SHEETS,
    ChannelObservation,
    ChannelSheet,
    DailyVersionPin,
    ErrorCode,
    MonthlyFact,
    MonthlyReport,
    MonthlyReportError,
    MonthlySnapshot,
)


INVALID_SHEET_CHARS = re.compile(r"[:\\/?*\[\]]")


def month_bounds(year: int, month: int) -> Tuple[date, date]:
    if year < 1900 or year > 9999 or month < 1 or month > 12:
        raise MonthlyReportError(ErrorCode.REQUEST_INVALID, "report month is invalid")
    return date(year, month, 1), date(year, month, calendar.monthrange(year, month)[1])


def validate_as_of(year: int, month: int, as_of_date: date) -> None:
    start, end = month_bounds(year, month)
    if as_of_date < start or as_of_date > end:
        raise MonthlyReportError(
            ErrorCode.REQUEST_INVALID,
            "as-of date must fall inside the report month",
        )


def _valid_sheet_name(value: str) -> bool:
    return (
        bool(value)
        and value == value.strip()
        and len(value) <= 31
        and INVALID_SHEET_CHARS.search(value) is None
    )


def _append_unique(target: List[str], seen: set[str], values: Iterable[str]) -> None:
    for value in values:
        if not _valid_sheet_name(value):
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "source channel worksheet name is invalid",
            )
        if value not in seen:
            target.append(value)
            seen.add(value)


def channel_order(snapshot: MonthlySnapshot) -> Tuple[str, ...]:
    ordered: List[str] = []
    seen: set[str] = set()
    _append_unique(ordered, seen, STANDARD_SHEETS)
    _append_unique(
        ordered,
        seen,
        (name for name in snapshot.preferred_channel_order if name not in STANDARD_SHEETS),
    )
    observations: Sequence[ChannelObservation] = sorted(
        snapshot.channel_observations,
        key=lambda item: (
            item.business_date,
            item.worksheet_order is None,
            item.worksheet_order if item.worksheet_order is not None else 2**31,
            item.worksheet,
        ),
    )
    _append_unique(ordered, seen, (item.worksheet for item in observations))
    _append_unique(
        ordered,
        seen,
        sorted({fact.channel_key for fact in snapshot.facts if fact.channel_key not in seen}),
    )
    return tuple(ordered)


def _require_nonnegative(value: int, label: str) -> None:
    if not isinstance(value, int) or isinstance(value, bool) or value < 0:
        raise MonthlyReportError(ErrorCode.SOURCE_INVALID, f"{label} is invalid")


def _validate_fact(fact: MonthlyFact, start: date, as_of_date: date) -> None:
    if (
        fact.daily_record_id <= 0
        or fact.daily_version_id <= 0
        or fact.business_date < start
        or fact.business_date > as_of_date
        or fact.arrival != fact.business_date
        or fact.departure < fact.arrival
        or fact.nights != (fact.departure - fact.arrival).days
        or not _valid_sheet_name(fact.channel_key)
        or fact.no_of_rooms <= 0
    ):
        raise MonthlyReportError(
            ErrorCode.SOURCE_INVALID,
            "monthly source fact shape is invalid",
        )
    _require_nonnegative(fact.adults, "adults")
    _require_nonnegative(fact.children, "children")
    for text in (
        fact.company_name,
        fact.confirmation_no,
        fact.disp_room_no,
        fact.full_name,
        fact.rate_code,
    ):
        if not str(text).strip():
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly source text is missing",
            )
    for amount in (
        fact.effective_rate_amount,
        fact.real_price,
        fact.total_price,
    ):
        if not isinstance(amount, Decimal) or not amount.is_finite() or amount < 0:
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly source amount is invalid",
            )
    if fact.total_price != fact.real_price * fact.no_of_rooms * fact.nights:
        raise MonthlyReportError(
            ErrorCode.SOURCE_INVALID,
            "monthly source total does not match static fact inputs",
        )
    if fact.channel_key == KB_SHEET:
        if fact.kb_amount != Decimal(fact.no_of_rooms * 100):
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly KB source amount is invalid",
            )
    elif fact.kb_amount is not None:
        raise MonthlyReportError(
            ErrorCode.SOURCE_INVALID,
            "non-KB channel unexpectedly contains a KB amount",
        )


def _validate_pins(
    facts: Sequence[MonthlyFact],
    pins: Sequence[DailyVersionPin],
    start: date,
    as_of_date: date,
) -> Tuple[DailyVersionPin, ...]:
    by_date: Dict[date, int] = {}
    for pin in pins:
        if (
            pin.business_date < start
            or pin.business_date > as_of_date
            or pin.daily_version_id <= 0
            or (
                pin.business_date in by_date
                and by_date[pin.business_date] != pin.daily_version_id
            )
        ):
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly daily-version manifest is invalid",
            )
        by_date[pin.business_date] = pin.daily_version_id
    for fact in facts:
        if by_date.get(fact.business_date) != fact.daily_version_id:
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly fact is not covered by the current daily manifest",
            )
    return tuple(
        DailyVersionPin(business_date=current, daily_version_id=version_id)
        for current, version_id in sorted(by_date.items())
    )


def _validate_observations(
    facts: Sequence[MonthlyFact],
    observations: Sequence[ChannelObservation],
    pins: Sequence[DailyVersionPin],
) -> None:
    pin_by_date = {pin.business_date: pin.daily_version_id for pin in pins}
    observed: Dict[Tuple[int, str], int] = {}
    orders: set[Tuple[int, int]] = set()
    for item in observations:
        if (
            pin_by_date.get(item.business_date) != item.daily_version_id
            or not _valid_sheet_name(item.worksheet)
            or item.row_count < 0
            or (item.worksheet_order is not None and item.worksheet_order <= 0)
        ):
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly channel observation is invalid",
            )
        key = (item.daily_version_id, item.worksheet)
        if key in observed:
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly channel observation is duplicated",
            )
        observed[key] = item.row_count
        if item.worksheet_order is not None:
            order_key = (item.daily_version_id, item.worksheet_order)
            if order_key in orders:
                raise MonthlyReportError(
                    ErrorCode.SOURCE_INVALID,
                    "monthly channel observation order is duplicated",
                )
            orders.add(order_key)

    actual: Dict[Tuple[int, str], int] = defaultdict(int)
    for fact in facts:
        actual[(fact.daily_version_id, fact.channel_key)] += 1
    for key, row_count in actual.items():
        if observed.get(key) != row_count:
            raise MonthlyReportError(
                ErrorCode.SOURCE_INVALID,
                "monthly channel observation does not match retained facts",
            )


def build_monthly_report(
    year: int,
    month: int,
    as_of_date: date,
    snapshot: MonthlySnapshot,
) -> MonthlyReport:
    validate_as_of(year, month, as_of_date)
    start, _end = month_bounds(year, month)
    facts = tuple(sorted(snapshot.facts, key=lambda item: item.daily_record_id))
    if len({fact.daily_record_id for fact in facts}) != len(facts):
        raise MonthlyReportError(ErrorCode.SOURCE_INVALID, "monthly source rows are duplicated")
    for fact in facts:
        _validate_fact(fact, start, as_of_date)
    if facts and max(fact.arrival for fact in facts) != as_of_date:
        raise MonthlyReportError(
            ErrorCode.SOURCE_INVALID,
            "monthly as-of date must equal the greatest included ARRIVAL",
        )
    pins = _validate_pins(facts, snapshot.daily_versions, start, as_of_date)
    _validate_observations(facts, snapshot.channel_observations, pins)
    ordered_channels = channel_order(snapshot)
    grouped: Dict[str, List[MonthlyFact]] = defaultdict(list)
    for fact in facts:
        grouped[fact.channel_key].append(fact)
    channels = tuple(
        ChannelSheet(
            worksheet=name,
            worksheet_order=index,
            rows=tuple(
                sorted(grouped.get(name, []), key=lambda item: (item.arrival, item.daily_record_id))
            ),
        )
        for index, name in enumerate(ordered_channels, 1)
    )
    if sum(len(channel.rows) for channel in channels) != len(facts):
        raise MonthlyReportError(
            ErrorCode.SOURCE_INVALID,
            "monthly source channel coverage is incomplete",
        )
    filename = (
        f"各渠道情况-{year:04d}年{month:02d}月-"
        f"更新至{as_of_date.month}.{as_of_date.day}.xlsx"
    )
    return MonthlyReport(
        report_year=year,
        report_month=month,
        as_of_date=as_of_date,
        filename=filename,
        channels=channels,
        daily_versions=pins,
    )
