"""Stable contracts and privacy-minimized data models for company reports."""

from __future__ import annotations

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


PROCESSOR_VERSION = "1.2.0"
RESULT_SCHEMA_VERSION = "1.0"

ENGLISH_MONTH_NAMES: Tuple[str, ...] = (
    "",
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
)

ENGLISH_MONTH_ABBREVIATIONS: Tuple[str, ...] = (
    "",
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
)

REPORT_HEADERS: Tuple[str, ...] = (
    "ARRIVAL",
    "DEPARTURE",
    "NIGHTS",
    "BLOCK_CODE",
    "RES_COMMENT",
    "Booking Room",
    "Total Booking Price",
)


@dataclass(frozen=True)
class CompanyRule:
    display_name: str
    match_field: str
    match_value: str


COMPANY_RULES: Tuple[CompanyRule, ...] = (
    CompanyRule("LianTai", "company_key", "LIAN TAI"),
    CompanyRule("QBD", "company_key", "QBD"),
    CompanyRule("DY-AI-Easy-KB", "channel_key", "DY-AI-Easy-KB"),
    CompanyRule("FengRun", "company_key", "FENGRUN"),
    CompanyRule("HanaTour", "company_key", "HANA TOUR"),
)

COMPANY_NAMES: Tuple[str, ...] = tuple(rule.display_name for rule in COMPANY_RULES)


class ErrorCode:
    REQUEST_INVALID = "COMPANY_REPORT_REQUEST_INVALID"
    SOURCE_VERSION_MISSING = "COMPANY_REPORT_SOURCE_VERSION_MISSING"
    GROUP_CODE_MISSING = "COMPANY_REPORT_GROUP_CODE_MISSING"
    GROUP_CODE_NOT_FOUND = "COMPANY_REPORT_GROUP_CODE_NOT_FOUND"
    BOOKING_PARSE_FAILED = "COMPANY_REPORT_BOOKING_PARSE_FAILED"
    ROOM_ITEMS_MISSING = "COMPANY_REPORT_ROOM_ITEMS_MISSING"
    STAY_DATE_INVALID = "COMPANY_REPORT_STAY_DATE_INVALID"
    NIGHTS_CONFLICT = "COMPANY_REPORT_NIGHTS_CONFLICT"
    TOTAL_PRICE_INVALID = "COMPANY_REPORT_TOTAL_PRICE_INVALID"
    OUTPUT_VALIDATION_FAILED = "COMPANY_REPORT_OUTPUT_VALIDATION_FAILED"
    PUBLISH_FAILED = "COMPANY_REPORT_PUBLISH_FAILED"
    INTERNAL_ERROR = "COMPANY_REPORT_INTERNAL_ERROR"


ERROR_CODES: Tuple[str, ...] = (
    ErrorCode.REQUEST_INVALID,
    ErrorCode.SOURCE_VERSION_MISSING,
    ErrorCode.GROUP_CODE_MISSING,
    ErrorCode.GROUP_CODE_NOT_FOUND,
    ErrorCode.BOOKING_PARSE_FAILED,
    ErrorCode.ROOM_ITEMS_MISSING,
    ErrorCode.STAY_DATE_INVALID,
    ErrorCode.NIGHTS_CONFLICT,
    ErrorCode.TOTAL_PRICE_INVALID,
    ErrorCode.OUTPUT_VALIDATION_FAILED,
    ErrorCode.PUBLISH_FAILED,
    ErrorCode.INTERNAL_ERROR,
)


class WarningCode:
    MULTI_PRICE_REVIEW = "COMPANY_REPORT_MULTI_PRICE_REVIEW"


WARNING_CODES: Tuple[str, ...] = (WarningCode.MULTI_PRICE_REVIEW,)


@dataclass(frozen=True)
class FinanceFact:
    daily_record_id: int
    daily_version_id: int
    business_date: date
    channel_key: str
    company_key: str
    company_name: str
    block_code: Optional[str]
    group_code_key: Optional[str]
    res_comment: Optional[str]
    room_category_label: Optional[str]
    arrival: Optional[date]
    departure: Optional[date]
    nights: Optional[int]
    total_price: Optional[Decimal]
    booking_source_match_status: str = "not_checked"


@dataclass(frozen=True)
class BookingRoomItem:
    group_code_key: str
    parse_version_id: int
    segment_no: int
    arrival: date
    departure: date
    nights: int
    item_no: int
    room_type_raw: str
    quantity: int


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


@dataclass(frozen=True)
class BatchSnapshot:
    facts: Tuple[FinanceFact, ...]
    room_items: Tuple[BookingRoomItem, ...]
    daily_versions: Tuple[DailyVersionPin, ...]
    group_parse_versions: Mapping[str, int]


@dataclass(frozen=True)
class ReportProblem:
    code: str
    stage: str
    company: str
    period: str
    message: str
    record_ids: Tuple[int, ...] = field(default_factory=tuple)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "code": self.code,
            "stage": self.stage,
            "company": self.company,
            "period": self.period,
            "message": self.message,
            "record_ids": list(self.record_ids),
        }


@dataclass(frozen=True)
class ReportRow:
    arrival: date
    departure: date
    nights: int
    block_code: str
    res_comment: str
    booking_room: str
    total_booking_price: str
    normalized_group_code: str
    record_ids: Tuple[int, ...]
    duplicate_group: bool = False
    multi_price_review: bool = False

    def to_payload(self) -> Dict[str, Any]:
        return {
            "arrival": self.arrival.isoformat(),
            "departure": self.departure.isoformat(),
            "nights": self.nights,
            "block_code": self.block_code,
            "res_comment": self.res_comment,
            "booking_room": self.booking_room,
            "total_booking_price": self.total_booking_price,
            "duplicate_group": self.duplicate_group,
            "multi_price_review": self.multi_price_review,
        }


@dataclass(frozen=True)
class PeriodReport:
    start_day: int
    end_day: int
    active: bool
    sheet_name: str
    rows: Tuple[ReportRow, ...]

    @property
    def key(self) -> str:
        return f"{self.start_day:02d}-{self.end_day:02d}"

    def to_payload(self) -> Dict[str, Any]:
        return {
            "period": self.key,
            "active": self.active,
            "sheet_name": self.sheet_name,
            "rows": [row.to_payload() for row in self.rows],
        }


@dataclass(frozen=True)
class CompanyReport:
    company: str
    report_year: int
    report_month: int
    as_of_date: date
    filename: str
    periods: Tuple[PeriodReport, ...]
    warnings: Tuple[ReportProblem, ...]
    errors: Tuple[ReportProblem, ...]
    daily_versions: Tuple[DailyVersionPin, ...]
    booking_versions: Mapping[str, int]

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

    @property
    def valid(self) -> bool:
        return not self.errors

    def to_workbook_payload(self) -> Dict[str, Any]:
        if not self.valid:
            raise ValueError("cannot build a workbook payload for an invalid company report")
        return {
            "schema_version": RESULT_SCHEMA_VERSION,
            "company": self.company,
            "report_year": self.report_year,
            "report_month": self.report_month,
            "as_of_date": self.as_of_date.isoformat(),
            "filename": self.filename,
            "headers": list(REPORT_HEADERS),
            "periods": [period.to_payload() for period in self.periods],
        }


def rule_set_sha256() -> str:
    payload = {
        "processor_version": PROCESSOR_VERSION,
        "result_schema_version": RESULT_SCHEMA_VERSION,
        "headers": REPORT_HEADERS,
        "companies": [rule.__dict__ for rule in COMPANY_RULES],
        "period_basis": "departure",
        "group_code_normalization": "nfkc_trim_upper",
        "row_key": {
            "with_group_code": ["group_code", "arrival", "departure"],
            "without_group_code": ["daily_record_id"],
        },
        "missing_group_code_booking_room": "blank",
        "unmatched_group_code_booking_room": "blank",
        "price_group_key": ["room_category_label", "static_total_price"],
        "filename_pattern": "<Company>-<EnglishFullMonth>-<YYYY>.xlsx",
        "sheet_pattern": "<Company> <DD-DD> <EnglishMon> <YYYY>",
        "periods": ["01-10", "11-20", "21-natural-month-end"],
        "styles": {
            "duplicate_fill": "#FFF2CC",
            "duplicate_group_font": "#9C6500",
            "multi_price_fill": "#FCE4D6",
            "multi_price_font": "#C65911",
            "header": "black_bold_no_colored_fill",
        },
    }
    encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()


def company_rule(name: str) -> CompanyRule:
    for rule in COMPANY_RULES:
        if rule.display_name == name:
            return rule
    raise ValueError(f"unsupported company: {name}")


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