from __future__ import annotations

import argparse
import json
import sys
import tempfile
import unittest
from datetime import date
from decimal import Decimal
from pathlib import Path

from openpyxl import Workbook, load_workbook


PROJECT_ROOT = Path(__file__).resolve().parents[1]
SKILL_ROOT = PROJECT_ROOT / "opera-daily-channel-report"
SCRIPTS = SKILL_ROOT / "scripts"
PRICE_REFERENCE = SKILL_ROOT / "references" / "价格对照.xlsx"

sys.path.insert(0, str(SCRIPTS))
import process_reports as core  # noqa: E402


def pricing_record(company: str, rate_code: str, amount: str = "999"):
    return {
        "_SOURCE_INDEX": 1,
        "COMPANY_NAME": company,
        "RATE_CODE": rate_code,
        "EFFECTIVE_RATE_AMOUNT": Decimal(amount),
        "CONFIRMATION_NO": "TEST-CONFIRMATION",
        "NO_OF_ROOMS": 1,
        "NIGHTS": 1,
        "REAL PRICE": None,
        "TOTAL PRICE": None,
    }


class PricingRuleTests(unittest.TestCase):
    def test_deterministic_keyword_company_matching(self):
        cases = {
            "T- LIAN TAI TRAVEL (T": "LIAN TAI",
            "T / Q.B.D. TRAVEL GROUP": "QBD",
            "T- Rainbow Holiday Service": "RAINBOW/AI",
            "T- FENG RUN TRAVEL": "FENGRUN",
            "T- HANATOUR TD CO., LTD": "HANA TOUR",
            "T / HANA TRAVEL": "HANA TOUR",
            "T- HONG TAI TRAVEL": "HONGTAI",
            "T / Guangzhou Go-Easy": "GUANGZHOU GO EASY",
            "Hong Yun": "HONGYUN",
        }
        for raw, expected in cases.items():
            with self.subTest(raw=raw):
                self.assertEqual(core.price_company_key(raw), expected)

    def test_multiple_keyword_groups_fail_instead_of_guessing(self):
        with self.assertRaises(core.CompanyKeywordAmbiguity) as caught:
            core.price_company_key("HANA HONGTAI")
        self.assertEqual(caught.exception.matches, ("HANA TOUR", "HONGTAI"))

        record = pricing_record("HANA HONGTAI", "LBMS")
        with self.assertRaises(core.ProcessingFailure) as caught_price:
            core.apply_prices([record], {})
        self.assertEqual(caught_price.exception.errors[0].code, "PRICE_COMPANY_AMBIGUOUS")

        with self.assertRaises(core.ProcessingFailure) as caught_route:
            core.route_standard("HANA HONGTAI", "LBMS")
        self.assertEqual(caught_route.exception.errors[0].code, "MONTHLY_COMPANY_AMBIGUOUS")

    def test_zero_price_requires_both_company_and_rate_conditions(self):
        records = [
            pricing_record("T / Guangzhou Go-Easy", "LBSM", "12345"),
            pricing_record("T- Rainbow Holiday Service", "lbms", "67890"),
        ]
        core.apply_prices(records, {})
        self.assertEqual([record["REAL PRICE"] for record in records], [Decimal(0), Decimal(0)])
        self.assertEqual([record["TOTAL PRICE"] for record in records], [Decimal(0), Decimal(0)])

        for company, rate_code in (
            ("T- Guangzhou Go Easy", "GRPA1"),
            ("T- Rainbow Holiday Se", "LBW1"),
            ("T- HANATOUR TD CO., L", "LBMS"),
        ):
            with self.subTest(company=company, rate_code=rate_code):
                with self.assertRaises(core.ProcessingFailure) as caught:
                    core.apply_prices([pricing_record(company, rate_code)], {})
                self.assertEqual(caught.exception.errors[0].code, "PRICE_UNMATCHED")

    def test_latest_price_reference_is_normalized_on_load(self):
        price_map = core.load_price_map(PRICE_REFERENCE.resolve())
        self.assertEqual(len(price_map), 73)
        rainbow_rules = [
            total for (company, _rate, _amount), total in price_map.items() if company == "RAINBOW/AI"
        ]
        self.assertEqual(len(rainbow_rules), 9)
        self.assertTrue(all(total == Decimal(0) for total in rainbow_rules))
        self.assertEqual(price_map[("RAINBOW/AI", "LBSM", Decimal("900"))], Decimal(0))
        self.assertEqual(price_map[("HANA TOUR", "WHKR2100B", Decimal("2100"))], Decimal("2100"))
        self.assertEqual(price_map[("HONGTAI", "LBKB", Decimal("900"))], Decimal("1800"))
        self.assertEqual(price_map[("FENGRUN", "GLSPCB", Decimal("1800"))], Decimal("1800"))

    def test_duplicate_detection_uses_normalized_company_identity(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            price_path = Path(temp_dir) / "duplicate.xlsx"
            workbook = Workbook()
            sheet = workbook.active
            sheet.title = "Sheet1"
            sheet.append(["COMPANY'S NAME", "RATE CODE", "Opera展示的价格", "总价"])
            sheet.append(["T- HANATOUR TD CO., L", "WHKR2100B", 2100, 2100])
            sheet.append(["HANA TOUR", "WHKR2100B", 2100, 2100])
            workbook.save(price_path)
            workbook.close()

            with self.assertRaises(core.ProcessingFailure) as caught:
                core.load_price_map(price_path.resolve())
            self.assertEqual(caught.exception.errors[0].code, "PRICE_DUPLICATE_KEY")

    def test_standard_route_uses_same_fuzzy_identity(self):
        cases = {
            ("T / Q.B.D. TRAVEL GROUP", "GRPA1"): "QBD",
            ("T- LIAN TAI TRAVEL", "LTLT"): "LIANTAI-FIT",
            ("T- LIAN TAI TRAVEL", "GRPA1"): "LIANTAI-GROUP",
            ("T- Rainbow Holiday Service", "LBW1"): "DY-AI-Easy-KB",
            ("T- FENG RUN TRAVEL", "GLSPCB"): "FENGRUN",
            ("T / Guangzhou Go-Easy", "GRPA1"): "DY-AI-Easy-KB",
        }
        for arguments, expected in cases.items():
            with self.subTest(arguments=arguments):
                self.assertEqual(core.route_standard(*arguments), expected)

    def test_legacy_monthly_header_contract_is_rejected(self):
        template = SKILL_ROOT / "assets" / "channel-report-template.xlsx"
        workbook = load_workbook(template, data_only=False)
        try:
            workbook[core.KB_SHEET].cell(1, 20).value = None
            with self.assertRaises(core.ProcessingFailure) as caught:
                core.read_existing_monthly(workbook, date(2026, 7, 27))
            self.assertIn(
                "MONTHLY_HEADER_MISMATCH",
                {error.code for error in caught.exception.errors},
            )
        finally:
            workbook.close()

    def test_existing_monthly_derived_values_are_revalidated(self):
        template = SKILL_ROOT / "assets" / "channel-report-template.xlsx"
        workbook = load_workbook(template, data_only=False)
        try:
            sheet = workbook[core.KB_SHEET]
            values = {
                "ARRIVAL": date(2026, 7, 26),
                "DEPARTURE": date(2026, 7, 27),
                "NIGHTS": 1,
                "ADULTS": 1,
                "CHILDREN": 0,
                "BLOCK_CODE": "",
                "NO_OF_ROOMS": 2,
                "COMPANY_NAME": "T- Rainbow Holiday Service",
                "CONFIRMATION_NO": "TEST-EXISTING",
                "DISP_ROOM_NO": "TEST-ROOM-EXISTING",
                "RATE_AMOUNT": 900,
                "FULL_NAME": "TEST GUEST EXISTING",
                "RES_COMMENT": "",
                "TRACE_TEXT": "",
                "PRODUCTS": "",
                "RATE_CODE": "LBMS",
                "ROOM_CATEGORY_LABEL": "TEST",
                "REAL PRICE": 0,
                "TOTAL PRICE": 1,
                core.KB_HEADER: 100,
            }
            for column, header in enumerate(core.KB_CHANNEL_HEADERS, 1):
                sheet.cell(2, column).value = values[header]
            with self.assertRaises(core.ProcessingFailure) as caught:
                core.read_existing_monthly(workbook, date(2026, 7, 27))
            codes = {error.code for error in caught.exception.errors}
            self.assertIn("MONTHLY_TOTAL_PRICE_INVALID", codes)
            self.assertIn("MONTHLY_KB_INVALID", codes)
        finally:
            workbook.close()

    def test_end_to_end_daily_and_monthly_zero_prices(self):
        xml = """<?xml version="1.0" encoding="UTF-8"?>
<RES_DETAIL>
  <LIST_G_GROUP_BY1>
    <G_GROUP_BY1>
      <GROUPBY1_SORT_COL>20260727</GROUPBY1_SORT_COL>
      <GROUPBY1_COL>27-07-26</GROUPBY1_COL>
      <LIST_G_RESERVATION>
        <G_RESERVATION>
          <ADULTS>2</ADULTS><BLOCK_CODE></BLOCK_CODE><CF_CHILDREN>0</CF_CHILDREN>
          <COMPANY_NAME>T / Guangzhou Go-Easy</COMPANY_NAME>
          <CONFIRMATION_NO>TEST-GOEASY</CONFIRMATION_NO><DISP_ROOM_NO>TEST-ROOM-1</DISP_ROOM_NO>
          <EFFECTIVE_RATE_AMOUNT>12345</EFFECTIVE_RATE_AMOUNT><FULL_NAME>TEST GUEST ONE</FULL_NAME>
          <NO_OF_ROOMS>1</NO_OF_ROOMS><PRODUCTS></PRODUCTS><RATE_CODE>LBSM</RATE_CODE>
          <ROOM_CATEGORY_LABEL>TEST</ROOM_CATEGORY_LABEL>
          <TRUNC_BEGIN>2026-07-27</TRUNC_BEGIN><TRUNC_END>2026-07-28</TRUNC_END>
        </G_RESERVATION>
        <G_RESERVATION>
          <ADULTS>1</ADULTS><BLOCK_CODE></BLOCK_CODE><CF_CHILDREN>1</CF_CHILDREN>
          <COMPANY_NAME>T- Rainbow Holiday Service</COMPANY_NAME>
          <CONFIRMATION_NO>TEST-RAINBOW</CONFIRMATION_NO><DISP_ROOM_NO>TEST-ROOM-2</DISP_ROOM_NO>
          <EFFECTIVE_RATE_AMOUNT>67890</EFFECTIVE_RATE_AMOUNT><FULL_NAME>TEST GUEST TWO</FULL_NAME>
          <NO_OF_ROOMS>1</NO_OF_ROOMS><PRODUCTS></PRODUCTS><RATE_CODE>LBMS</RATE_CODE>
          <ROOM_CATEGORY_LABEL>TEST</ROOM_CATEGORY_LABEL>
          <TRUNC_BEGIN>2026-07-27</TRUNC_BEGIN><TRUNC_END>2026-07-29</TRUNC_END>
        </G_RESERVATION>
        <G_RESERVATION>
          <ADULTS>2</ADULTS><BLOCK_CODE>TEST-BLOCK</BLOCK_CODE><CF_CHILDREN>0</CF_CHILDREN>
          <COMPANY_NAME>T / Q.B.D. TRAVEL GROUP</COMPANY_NAME>
          <CONFIRMATION_NO>TEST-QBD</CONFIRMATION_NO><DISP_ROOM_NO>TEST-ROOM-3</DISP_ROOM_NO>
          <EFFECTIVE_RATE_AMOUNT>900</EFFECTIVE_RATE_AMOUNT><FULL_NAME>TEST GUEST THREE</FULL_NAME>
          <NO_OF_ROOMS>2</NO_OF_ROOMS><PRODUCTS></PRODUCTS><RATE_CODE>GRPA1</RATE_CODE>
          <ROOM_CATEGORY_LABEL>TEST</ROOM_CATEGORY_LABEL>
          <TRUNC_BEGIN>2026-07-27</TRUNC_BEGIN><TRUNC_END>2026-07-30</TRUNC_END>
        </G_RESERVATION>
      </LIST_G_RESERVATION>
    </G_GROUP_BY1>
  </LIST_G_GROUP_BY1>
</RES_DETAIL>
"""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            xml_path = temp_path / "synthetic.xml"
            output_dir = temp_path / "output"
            result_path = output_dir / "result.json"
            xml_path.write_text(xml, encoding="utf-8")
            exit_code = core.process(
                argparse.Namespace(
                    xml=str(xml_path.resolve()),
                    monthly_base=None,
                    output_dir=str(output_dir.resolve()),
                    result_json=str(result_path.resolve()),
                )
            )
            self.assertEqual(exit_code, 0)
            result = json.loads(result_path.read_text(encoding="utf-8"))
            self.assertEqual(result["status"], "success")

            daily_path = output_dir / result["outputs"]["daily_report"]
            monthly_path = output_dir / result["outputs"]["monthly_report"]
            daily = load_workbook(daily_path, data_only=False)
            try:
                confirmation_column = core.DAILY_HEADERS.index("CONFIRMATION_NO") + 1
                real_price_column = core.DAILY_HEADERS.index("REAL PRICE") + 1
                total_column = core.DAILY_HEADERS.index("TOTAL PRICE") + 1
                prices = {
                    daily.active.cell(row, confirmation_column).value: (
                        daily.active.cell(row, real_price_column).value,
                        daily.active.cell(row, total_column).value,
                    )
                    for row in range(2, daily.active.max_row + 1)
                    if daily.active.cell(row, confirmation_column).value
                }
                self.assertEqual(
                    prices,
                    {
                        "TEST-GOEASY": (0, 0),
                        "TEST-RAINBOW": (0, 0),
                        "TEST-QBD": (1800, 10800),
                    },
                )
                for row in range(2, daily.active.max_row + 1):
                    if daily.active.cell(row, confirmation_column).value:
                        for column in (real_price_column, total_column):
                            self.assertNotEqual(daily.active.cell(row, column).data_type, "f")
            finally:
                daily.close()

            monthly = load_workbook(monthly_path, data_only=False)
            try:
                self.assertIn(core.KB_SHEET, monthly.sheetnames)
                target = monthly[core.KB_SHEET]
                companies = [
                    target.cell(row, 8).value
                    for row in range(2, target.max_row + 1)
                    if target.cell(row, 8).value
                ]
                self.assertCountEqual(
                    companies,
                    ["T / Guangzhou Go-Easy", "T- Rainbow Holiday Service"],
                )
                target_values = {
                    target.cell(row, 9).value: (
                        target.cell(row, 18).value,
                        target.cell(row, 19).value,
                        target.cell(row, 20).value,
                    )
                    for row in range(2, target.max_row + 1)
                    if target.cell(row, 9).value
                }
                self.assertEqual(
                    target_values,
                    {
                        "TEST-GOEASY": (0, 0, 100),
                        "TEST-RAINBOW": (0, 0, 100),
                    },
                )

                qbd = monthly["QBD"]
                self.assertEqual(qbd.cell(2, 9).value, "TEST-QBD")
                self.assertEqual(qbd.cell(2, 18).value, 1800)
                self.assertEqual(qbd.cell(2, 19).value, 10800)
                for cell in (qbd.cell(2, 18), qbd.cell(2, 19)):
                    self.assertNotEqual(cell.data_type, "f")
                for row in range(2, target.max_row + 1):
                    for column in (18, 19, 20):
                        self.assertNotEqual(target.cell(row, column).data_type, "f")
            finally:
                monthly.close()


if __name__ == "__main__":
    unittest.main(verbosity=2)
