"""Typed contracts for database-backed monthly channel reports."""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from typing import Any, Dict, Mapping, Optional, Sequence, Tuple


PROCESSOR_VERSION = "1.0.0"
RESULT_SCHEMA_VERSION = "1.0"
XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"

CHANNEL_HEADERS: Tuple[str, ...] = (
    "ARRIVAL",
    "DEPARTURE",
    "NIGHTS",
    "ADULTS",
    "CHILDREN",
    "BLOCK_CODE",
    "NO_OF_ROOMS",
    "COMPANY_NAME",
    "CONFIRMATION_NO",
    "DISP_ROOM_NO",
    "RATE_AMOUNT",
    "FULL_NAME",
    "RES_COMMENT",
    "TRACE_TEXT",
    "PRODUCTS",
    "RATE_CODE",
    "ROOM_CATEGORY_LABEL",
    "REAL PRICE",
    "TOTAL PRICE",
)
KB_HEADER = "KB（100/晚/间）"
KB_SHEET = "DY-AI-Easy-KB"
KB_CHANNEL_HEADERS: Tuple[str, ...] = CHANNEL_HEADERS + (KB_HEADER,)
STANDARD_SHEETS: Tuple[str, ...] = (
    "LIANTAI-GROUP",
    "LIANTAI-FIT",
    "QBD",
    KB_SHEET,
    "FENGRUN",
)


class ErrorCode:
    REQUEST_INVALID = "MONTHLY_REPORT_REQUEST_INVALID"
    SOURCE_INVALID = "MONTHLY_REPORT_SOURCE_INVALID"
    SOURCE_SNAPSHOT_STALE = "MONTHLY_REPORT_SOURCE_SNAPSHOT_STALE"
    OUTPUT_VALIDATION_FAILED = "MONTHLY_REPORT_OUTPUT_VALIDATION_FAILED"
    PUBLISH_FAILED = "MONTHLY_REPORT_PUBLISH_FAILED"
    DATABASE_FAILED = "MONTHLY_REPORT_DATABASE_FAILED"
    INTERNAL_ERROR = "MONTHLY_REPORT_INTERNAL_ERROR"


class MonthlyReportError(RuntimeError):
    def __init__(self, code: str, safe_message: str):
        super().__init__(safe_message)
        self.code = code
        self.safe_message = safe_message


@dataclass(frozen=True)
class DailyVersionPin:
    business_date: date
    daily_version_id: int


@dataclass(frozen=True)
class ChannelObservation:
    business_date: date
    daily_version_id: int
    worksheet: str
    worksheet_order: Optional[int]
    row_count: int


@dataclass(frozen=True)
class MonthlyFact:
    daily_record_id: int
    daily_version_id: int
    business_date: date
    channel_key: str
    arrival: date
    departure: date
    nights: int
    adults: int
    children: int
    block_code: Optional[str]
    no_of_rooms: int
    company_name: str
    confirmation_no: str
    disp_room_no: str
    effective_rate_amount: Decimal
    full_name: str
    res_comment: Optional[str]
    trace_text: Optional[str]
    products: Optional[str]
    rate_code: str
    room_category_label: Optional[str]
    real_price: Decimal
    total_price: Decimal
    kb_amount: Optional[Decimal]

    @staticmethod
    def _decimal_text(value: Decimal) -> str:
        rendered = format(value, "f")
        if "." in rendered:
            rendered = rendered.rstrip("0").rstrip(".")
        return rendered or "0"

    def to_payload(self, include_kb: bool) -> Dict[str, Any]:
        payload: Dict[str, Any] = {
            "ARRIVAL": self.arrival.isoformat(),
            "DEPARTURE": self.departure.isoformat(),
            "NIGHTS": self.nights,
            "ADULTS": self.adults,
            "CHILDREN": self.children,
            "BLOCK_CODE": self.block_code or "",
            "NO_OF_ROOMS": self.no_of_rooms,
            "COMPANY_NAME": self.company_name,
            "CONFIRMATION_NO": self.confirmation_no,
            "DISP_ROOM_NO": self.disp_room_no,
            "RATE_AMOUNT": self._decimal_text(self.effective_rate_amount),
            "FULL_NAME": self.full_name,
            "RES_COMMENT": self.res_comment or "",
            "TRACE_TEXT": self.trace_text or "",
            "PRODUCTS": self.products or "",
            "RATE_CODE": self.rate_code,
            "ROOM_CATEGORY_LABEL": self.room_category_label or "",
            "REAL PRICE": self._decimal_text(self.real_price),
            "TOTAL PRICE": self._decimal_text(self.total_price),
        }
        if include_kb:
            if self.kb_amount is None:
                raise MonthlyReportError(
                    ErrorCode.SOURCE_INVALID,
                    "KB channel source amount is missing",
                )
            payload[KB_HEADER] = self._decimal_text(self.kb_amount)
        return payload


@dataclass(frozen=True)
class MonthlySnapshot:
    facts: Tuple[MonthlyFact, ...]
    daily_versions: Tuple[DailyVersionPin, ...]
    channel_observations: Tuple[ChannelObservation, ...]
    preferred_channel_order: Tuple[str, ...] = tuple()


@dataclass(frozen=True)
class ChannelSheet:
    worksheet: str
    worksheet_order: int
    rows: Tuple[MonthlyFact, ...]

    @property
    def headers(self) -> Tuple[str, ...]:
        return KB_CHANNEL_HEADERS if self.worksheet == KB_SHEET else CHANNEL_HEADERS

    def to_payload(self) -> Dict[str, Any]:
        include_kb = self.worksheet == KB_SHEET
        return {
            "worksheet": self.worksheet,
            "worksheet_order": self.worksheet_order,
            "headers": list(self.headers),
            "rows": [row.to_payload(include_kb) for row in self.rows],
        }


@dataclass(frozen=True)
class MonthlyReport:
    report_year: int
    report_month: int
    as_of_date: date
    filename: str
    channels: Tuple[ChannelSheet, ...]
    daily_versions: Tuple[DailyVersionPin, ...]

    @property
    def row_count(self) -> int:
        return sum(len(channel.rows) for channel in self.channels)

    @property
    def channel_manifest(self) -> Tuple[Tuple[str, int, int], ...]:
        return tuple(
            (channel.worksheet, channel.worksheet_order, len(channel.rows))
            for channel in self.channels
        )

    def to_workbook_payload(self) -> Dict[str, Any]:
        return {
            "schema_version": RESULT_SCHEMA_VERSION,
            "report_year": self.report_year,
            "report_month": self.report_month,
            "as_of_date": self.as_of_date.isoformat(),
            "filename": self.filename,
            "channels": [channel.to_payload() for channel in self.channels],
        }


def rule_set_sha256() -> str:
    payload: Mapping[str, Any] = {
        "processor_version": PROCESSOR_VERSION,
        "result_schema_version": RESULT_SCHEMA_VERSION,
        "headers": CHANNEL_HEADERS,
        "kb_header": KB_HEADER,
        "standard_sheets": STANDARD_SHEETS,
        "source": "finance.v_active_daily_facts",
        "row_order": ["arrival", "daily_record_id"],
        "unknown_channel_order": [
            "current_report_manifest",
            "daily_worksheet_first_seen",
            "lexical_fallback",
        ],
        "filename": "各渠道情况-YYYY年MM月-更新至M.D.xlsx",
        "formula_policy": "total_price_equals_real_price_times_nights_times_rooms_v1",
    }
    return hashlib.sha256(
        json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8")
    ).hexdigest()


def immutable_tuple(values: Sequence[Any]) -> Tuple[Any, ...]:
    return tuple(values)
