"""
咖啡店 POS 系統 — CustomTkinter
功能：
  • 菜單點餐（4 大類）
  • 品項備註（少冰、加糖…）
  • 折扣輸入（% 或固定金額）
  • 收款 / 找零計算
  • 結帳後跳出收據視窗
  • 交易紀錄自動存入 ./POS紀錄/<日期>.txt
  • 程式內查看今日紀錄
"""

import customtkinter as ctk
import tkinter as tk
from tkinter import messagebox
from datetime import datetime
import os, re

# ── 主題 ─────────────────────────────────────────────────
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

C = {
    "bg":      "#14100e",
    "panel":   "#1e1812",
    "card":    "#252015",
    "border":  "#3a3020",
    "accent":  "#c8924a",   # 咖啡金
    "accent2": "#e8b86d",
    "green":   "#5dba7a",
    "red":     "#d9534f",
    "text":    "#f0e8d8",
    "sub":     "#9a8a70",
    "tag":     "#3a2a18",
}

FONT_H  = ("Georgia",    16, "bold")
FONT_M  = ("Georgia",    13)
FONT_S  = ("Consolas",   12)
FONT_XS = ("Consolas",   11)

# ── 菜單資料 ──────────────────────────────────────────────
MENU: dict[str, list[tuple[str, int]]] = {
    "☕ 手沖系列": [
        ("手沖咖啡 A", 65),
        ("手沖咖啡 B", 45),
        ("手沖咖啡 C", 50),
    ],
    "🥛 拿鐵系列": [
        ("拿鐵系列 A",  90),
        ("拿鐵系列 B", 120),
        ("拿鐵系列 C",  90),
    ],
    "🍰 甜點系列": [
        ("提拉米蘇", 130),
        ("肉桂捲",   130),
    ],
    "🥟 主食系列": [
        ("高麗菜水餃 (10顆)", 100),
        ("韭菜水餃 (10顆)",   100),
    ],
}

QUICK_NOTES = ["少冰", "去冰", "熱的", "加糖", "少糖", "無糖", "加大", "外帶"]

RECORD_DIR = "POS紀錄"


# ═══════════════════════════════════════════════════════════
#  備註對話框
# ═══════════════════════════════════════════════════════════
class NoteDialog(ctk.CTkToplevel):
    def __init__(self, master, item_name: str, current_note: str = ""):
        super().__init__(master)
        self.title("備註")
        self.geometry("360x300")
        self.configure(fg_color=C["panel"])
        self.grab_set()
        self.result: str | None = None

        ctk.CTkLabel(self, text=f"備註：{item_name}",
                     font=FONT_H, text_color=C["accent2"]).pack(pady=(18, 8))

        # 快速標籤
        tag_frame = ctk.CTkFrame(self, fg_color="transparent")
        tag_frame.pack(fill="x", padx=20)
        for i, n in enumerate(QUICK_NOTES):
            ctk.CTkButton(tag_frame, text=n, width=64, height=26,
                          font=FONT_XS,
                          fg_color=C["tag"], hover_color=C["border"],
                          text_color=C["text"],
                          command=lambda v=n: self._append(v)
                          ).grid(row=i//4, column=i%4, padx=3, pady=3)

        self._entry = ctk.CTkEntry(self, placeholder_text="自由輸入…",
                                   font=FONT_S,
                                   fg_color=C["bg"], border_color=C["border"],
                                   text_color=C["text"])
        self._entry.pack(fill="x", padx=20, pady=(10, 0))
        if current_note:
            self._entry.insert(0, current_note)

        btn_row = ctk.CTkFrame(self, fg_color="transparent")
        btn_row.pack(pady=14)
        ctk.CTkButton(btn_row, text="確認", width=90,
                      fg_color=C["accent"], hover_color=C["accent2"],
                      text_color="#fff", font=FONT_S,
                      command=self._confirm).pack(side="left", padx=6)
        ctk.CTkButton(btn_row, text="清除", width=90,
                      fg_color=C["border"], hover_color=C["red"],
                      text_color=C["text"], font=FONT_S,
                      command=self._clear).pack(side="left", padx=6)

    def _append(self, v: str):
        cur = self._entry.get().strip()
        self._entry.delete(0, "end")
        self._entry.insert(0, (cur + " " + v).strip())

    def _confirm(self):
        self.result = self._entry.get().strip()
        self.destroy()

    def _clear(self):
        self.result = ""
        self.destroy()


# ═══════════════════════════════════════════════════════════
#  收據視窗
# ═══════════════════════════════════════════════════════════
class ReceiptWindow(ctk.CTkToplevel):
    def __init__(self, master, order: list, discount_amt: int,
                 total: int, received: int, change: int, order_no: int):
        super().__init__(master)
        self.title("收據")
        self.geometry("380x540")
        self.configure(fg_color=C["panel"])
        self.grab_set()
        self._master_ref = master

        now = datetime.now().strftime("%Y-%m-%d  %H:%M:%S")

        ctk.CTkLabel(self, text="☕  島嶼咖啡",
                     font=("Georgia", 22, "bold"),
                     text_color=C["accent2"]).pack(pady=(20, 2))
        ctk.CTkLabel(self, text=now, font=FONT_XS,
                     text_color=C["sub"]).pack()
        ctk.CTkLabel(self, text=f"訂單 #{order_no:04d}", font=FONT_XS,
                     text_color=C["sub"]).pack(pady=(0, 10))

        sep = lambda: ctk.CTkLabel(self, text="─"*42, font=("Consolas", 10),
                                    text_color=C["border"]).pack()
        sep()

        scroll = ctk.CTkScrollableFrame(self, fg_color="transparent", height=160)
        scroll.pack(fill="x", padx=20)

        for item in order:
            row = ctk.CTkFrame(scroll, fg_color="transparent")
            row.pack(fill="x", pady=1)
            note_str = f"  [{item['note']}]" if item['note'] else ""
            ctk.CTkLabel(row,
                         text=f"{item['name']}{note_str}  ×{item['qty']}",
                         font=FONT_XS, text_color=C["text"],
                         anchor="w").pack(side="left")
            ctk.CTkLabel(row,
                         text=f"${item['price']*item['qty']}",
                         font=FONT_XS, text_color=C["text"],
                         anchor="e").pack(side="right")

        sep()

        def info_row(label, value, color=None):
            r = ctk.CTkFrame(self, fg_color="transparent")
            r.pack(fill="x", padx=24, pady=1)
            ctk.CTkLabel(r, text=label, font=FONT_S,
                         text_color=C["sub"]).pack(side="left")
            ctk.CTkLabel(r, text=value, font=FONT_S,
                         text_color=color or C["text"]).pack(side="right")

        subtotal = sum(i["price"]*i["qty"] for i in order)
        info_row("小計", f"${subtotal}")
        if discount_amt:
            info_row("折扣", f"-${discount_amt}", C["green"])
        info_row("總計", f"${total}", C["accent2"])
        info_row("收款", f"${received}")
        info_row("找零", f"${change}", C["green"])

        sep()

        ctk.CTkLabel(self, text="謝謝光臨  歡迎再來 ☕",
                     font=FONT_M, text_color=C["sub"]).pack(pady=8)

        ctk.CTkButton(self, text="關閉並清空訂單",
                      font=FONT_S,
                      fg_color=C["accent"], hover_color=C["accent2"],
                      text_color="#fff",
                      command=self._close).pack(pady=(4, 20))

    def _close(self):
        self._master_ref.clear_order()
        self.destroy()


# ═══════════════════════════════════════════════════════════
#  今日紀錄視窗
# ═══════════════════════════════════════════════════════════
class LogWindow(ctk.CTkToplevel):
    def __init__(self, master):
        super().__init__(master)
        self.title("今日交易紀錄")
        self.geometry("640x480")
        self.configure(fg_color=C["panel"])

        today = datetime.now().strftime("%Y-%m-%d")
        path  = os.path.join(RECORD_DIR, f"{today}.txt")

        ctk.CTkLabel(self, text=f"📋  {today} 交易紀錄",
                     font=FONT_H, text_color=C["accent2"]).pack(pady=(16, 8))

        box = ctk.CTkTextbox(self, font=("Consolas", 11),
                             fg_color=C["bg"],
                             text_color=C["text"],
                             border_color=C["border"],
                             wrap="none")
        box.pack(fill="both", expand=True, padx=16, pady=(0, 16))

        if os.path.exists(path):
            with open(path, encoding="utf-8") as f:
                content = f.read()
            box.insert("end", content)
        else:
            box.insert("end", "（今日尚無交易紀錄）")
        box.configure(state="disabled")


# ═══════════════════════════════════════════════════════════
#  主視窗
# ═══════════════════════════════════════════════════════════
class POSApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("島嶼咖啡 POS")
        self.geometry("1100x720")
        self.minsize(900, 600)
        self.configure(fg_color=C["bg"])

        os.makedirs(RECORD_DIR, exist_ok=True)

        # 訂單資料  {name: {price, qty, note}}
        self._order: dict[str, dict] = {}
        self._order_no = self._load_order_no()

        self._build()

    # ── 訂單號碼 ─────────────────────────────────────────
    def _load_order_no(self) -> int:
        today = datetime.now().strftime("%Y-%m-%d")
        path  = os.path.join(RECORD_DIR, f"{today}.txt")
        if not os.path.exists(path):
            return 0
        with open(path, encoding="utf-8") as f:
            lines = [l for l in f.readlines() if l.strip()]
        return len(lines)

    # ── 建構 UI ──────────────────────────────────────────
    def _build(self):
        # 頂欄
        topbar = ctk.CTkFrame(self, fg_color=C["panel"],
                               corner_radius=0, height=52)
        topbar.pack(fill="x")
        topbar.pack_propagate(False)

        ctk.CTkLabel(topbar, text="☕  島嶼咖啡 POS",
                     font=("Georgia", 18, "bold"),
                     text_color=C["accent2"]).pack(side="left", padx=20)

        ctk.CTkButton(topbar, text="📋 今日紀錄", width=110, height=32,
                      font=FONT_XS,
                      fg_color=C["tag"], hover_color=C["border"],
                      text_color=C["text"],
                      command=lambda: LogWindow(self)).pack(side="right", padx=12)

        now_label = ctk.CTkLabel(topbar, text="", font=FONT_XS,
                                  text_color=C["sub"])
        now_label.pack(side="right", padx=8)
        self._clock_label = now_label
        self._tick_clock()

        # 主體：左（菜單）＋ 右（訂單）
        body = ctk.CTkFrame(self, fg_color="transparent")
        body.pack(fill="both", expand=True, padx=12, pady=10)
        body.columnconfigure(0, weight=3)
        body.columnconfigure(1, weight=2)
        body.rowconfigure(0, weight=1)

        self._build_menu(body)
        self._build_order(body)

    # ── 時鐘 ─────────────────────────────────────────────
    def _tick_clock(self):
        self._clock_label.configure(
            text=datetime.now().strftime("%Y-%m-%d  %H:%M:%S"))
        self.after(1000, self._tick_clock)

    # ── 左側菜單 ─────────────────────────────────────────
    def _build_menu(self, parent):
        frame = ctk.CTkScrollableFrame(parent, fg_color="transparent",
                                        scrollbar_button_color=C["border"])
        frame.grid(row=0, column=0, sticky="nsew", padx=(0, 8))

        for category, items in MENU.items():
            # 分類標題
            ctk.CTkLabel(frame, text=category,
                         font=("Georgia", 15, "bold"),
                         text_color=C["accent"],
                         anchor="w").pack(fill="x", padx=4, pady=(14, 4))

            grid = ctk.CTkFrame(frame, fg_color="transparent")
            grid.pack(fill="x", padx=4)

            for idx, (name, price) in enumerate(items):
                card = ctk.CTkFrame(grid, fg_color=C["card"],
                                    corner_radius=10,
                                    border_width=1, border_color=C["border"])
                card.grid(row=idx//3, column=idx%3,
                          padx=5, pady=5, sticky="ew")
                grid.columnconfigure(idx%3, weight=1)

                ctk.CTkLabel(card, text=name,
                             font=FONT_S, text_color=C["text"],
                             wraplength=130, justify="center").pack(pady=(12, 2))
                ctk.CTkLabel(card, text=f"${price}",
                             font=("Georgia", 14, "bold"),
                             text_color=C["accent2"]).pack(pady=(0, 8))

                btn_row = ctk.CTkFrame(card, fg_color="transparent")
                btn_row.pack(pady=(0, 10))

                ctk.CTkButton(btn_row, text="＋", width=36, height=30,
                              font=("Consolas", 14, "bold"),
                              fg_color=C["accent"], hover_color=C["accent2"],
                              text_color="#fff",
                              command=lambda n=name, p=price: self._add(n, p)
                              ).pack(side="left", padx=2)
                ctk.CTkButton(btn_row, text="備註", width=46, height=30,
                              font=FONT_XS,
                              fg_color=C["tag"], hover_color=C["border"],
                              text_color=C["text"],
                              command=lambda n=name, p=price: self._open_note(n, p)
                              ).pack(side="left", padx=2)

    # ── 右側訂單 ─────────────────────────────────────────
    def _build_order(self, parent):
        frame = ctk.CTkFrame(parent, fg_color=C["panel"],
                              corner_radius=12,
                              border_width=1, border_color=C["border"])
        frame.grid(row=0, column=1, sticky="nsew")
        frame.rowconfigure(1, weight=1)

        ctk.CTkLabel(frame, text="訂單",
                     font=FONT_H, text_color=C["accent2"]).grid(
            row=0, column=0, sticky="w", padx=16, pady=(14, 4))

        # 訂單列表
        self._order_scroll = ctk.CTkScrollableFrame(
            frame, fg_color="transparent",
            scrollbar_button_color=C["border"])
        self._order_scroll.grid(row=1, column=0, sticky="nsew", padx=10)
        frame.columnconfigure(0, weight=1)

        self._order_rows: dict[str, ctk.CTkFrame] = {}

        # ── 折扣區 ───────────────────────────────────────
        disc_frame = ctk.CTkFrame(frame, fg_color=C["card"],
                                   corner_radius=8)
        disc_frame.grid(row=2, column=0, sticky="ew", padx=10, pady=(6, 0))

        ctk.CTkLabel(disc_frame, text="折扣",
                     font=FONT_XS, text_color=C["sub"]).grid(
            row=0, column=0, padx=10, pady=6, sticky="w")

        self._disc_var  = ctk.StringVar(value="")
        self._disc_type = ctk.StringVar(value="amount")

        ctk.CTkEntry(disc_frame, textvariable=self._disc_var,
                     placeholder_text="輸入折扣…",
                     width=100, font=FONT_S,
                     fg_color=C["bg"], border_color=C["border"],
                     text_color=C["text"]).grid(row=0, column=1, padx=4)

        ctk.CTkRadioButton(disc_frame, text="固定 $",
                           variable=self._disc_type, value="amount",
                           font=FONT_XS, text_color=C["text"]).grid(
            row=0, column=2, padx=4)
        ctk.CTkRadioButton(disc_frame, text="百分比 %",
                           variable=self._disc_type, value="percent",
                           font=FONT_XS, text_color=C["text"]).grid(
            row=0, column=3, padx=4)

        # ── 收款區 ───────────────────────────────────────
        pay_frame = ctk.CTkFrame(frame, fg_color=C["card"], corner_radius=8)
        pay_frame.grid(row=3, column=0, sticky="ew", padx=10, pady=6)

        ctk.CTkLabel(pay_frame, text="小計", font=FONT_XS,
                     text_color=C["sub"]).grid(row=0, column=0, padx=10, pady=4, sticky="w")
        self._subtotal_lbl = ctk.CTkLabel(pay_frame, text="$0",
                                           font=FONT_S, text_color=C["text"])
        self._subtotal_lbl.grid(row=0, column=1, sticky="e", padx=10)

        ctk.CTkLabel(pay_frame, text="折扣後", font=FONT_XS,
                     text_color=C["sub"]).grid(row=1, column=0, padx=10, pady=2, sticky="w")
        self._total_lbl = ctk.CTkLabel(pay_frame, text="$0",
                                        font=("Georgia", 15, "bold"),
                                        text_color=C["accent2"])
        self._total_lbl.grid(row=1, column=1, sticky="e", padx=10)

        pay_frame.columnconfigure(1, weight=1)

        recv_row = ctk.CTkFrame(pay_frame, fg_color="transparent")
        recv_row.grid(row=2, column=0, columnspan=2, sticky="ew", padx=8, pady=(4, 8))

        ctk.CTkLabel(recv_row, text="收款 $",
                     font=FONT_S, text_color=C["text"]).pack(side="left")
        self._recv_entry = ctk.CTkEntry(recv_row, width=90, font=FONT_S,
                                         fg_color=C["bg"],
                                         border_color=C["border"],
                                         text_color=C["text"],
                                         placeholder_text="0")
        self._recv_entry.pack(side="left", padx=6)
        self._recv_entry.bind("<KeyRelease>", lambda e: self._refresh_change())

        ctk.CTkLabel(recv_row, text="找零",
                     font=FONT_S, text_color=C["sub"]).pack(side="left", padx=(12, 4))
        self._change_lbl = ctk.CTkLabel(recv_row, text="$—",
                                         font=("Georgia", 14, "bold"),
                                         text_color=C["green"])
        self._change_lbl.pack(side="left")

        # ── 操作按鈕 ─────────────────────────────────────
        btn_frame = ctk.CTkFrame(frame, fg_color="transparent")
        btn_frame.grid(row=4, column=0, pady=(0, 12))

        ctk.CTkButton(btn_frame, text="結帳", width=120, height=40,
                      font=("Georgia", 14, "bold"),
                      fg_color=C["accent"], hover_color=C["accent2"],
                      text_color="#fff",
                      command=self._checkout).pack(side="left", padx=6)
        ctk.CTkButton(btn_frame, text="清空訂單", width=100, height=40,
                      font=FONT_S,
                      fg_color=C["border"], hover_color=C["red"],
                      text_color=C["text"],
                      command=self.clear_order).pack(side="left", padx=6)

    # ── 加入品項 ─────────────────────────────────────────
    def _add(self, name: str, price: int, note: str = ""):
        if name in self._order:
            self._order[name]["qty"] += 1
            self._update_row(name)
        else:
            self._order[name] = {"price": price, "qty": 1, "note": note}
            self._add_row(name)
        self._refresh_total()

    def _open_note(self, name: str, price: int):
        cur_note = self._order.get(name, {}).get("note", "")
        dlg = NoteDialog(self, name, cur_note)
        self.wait_window(dlg)
        if dlg.result is not None:
            if name not in self._order:
                self._order[name] = {"price": price, "qty": 1, "note": dlg.result}
                self._add_row(name)
            else:
                self._order[name]["note"] = dlg.result
                self._update_row(name)
            self._refresh_total()

    # ── 訂單 Row ─────────────────────────────────────────
    def _add_row(self, name: str):
        row = ctk.CTkFrame(self._order_scroll, fg_color=C["card"],
                            corner_radius=8)
        row.pack(fill="x", pady=3)
        self._order_rows[name] = row
        self._render_row(name)

    def _update_row(self, name: str):
        row = self._order_rows[name]
        for w in row.winfo_children():
            w.destroy()
        self._render_row(name)

    def _render_row(self, name: str):
        item = self._order[name]
        row  = self._order_rows[name]

        info = ctk.CTkFrame(row, fg_color="transparent")
        info.pack(fill="x", padx=8, pady=(6, 0))

        ctk.CTkLabel(info, text=name, font=FONT_S,
                     text_color=C["text"], anchor="w").pack(side="left")
        ctk.CTkLabel(info, text=f"${item['price'] * item['qty']}",
                     font=("Georgia", 13, "bold"),
                     text_color=C["accent2"]).pack(side="right")

        ctrl = ctk.CTkFrame(row, fg_color="transparent")
        ctrl.pack(fill="x", padx=8, pady=(2, 6))

        # note
        note_text = item["note"] if item["note"] else "無備註"
        note_color = C["accent"] if item["note"] else C["sub"]
        ctk.CTkLabel(ctrl, text=note_text, font=FONT_XS,
                     text_color=note_color).pack(side="left")

        # qty controls
        qty_box = ctk.CTkFrame(ctrl, fg_color="transparent")
        qty_box.pack(side="right")

        ctk.CTkButton(qty_box, text="−", width=26, height=24,
                      font=("Consolas", 13, "bold"),
                      fg_color=C["border"], hover_color=C["red"],
                      text_color=C["text"],
                      command=lambda n=name: self._dec(n)
                      ).pack(side="left", padx=1)
        ctk.CTkLabel(qty_box, text=str(item["qty"]),
                     width=28, font=FONT_S,
                     text_color=C["text"]).pack(side="left")
        ctk.CTkButton(qty_box, text="＋", width=26, height=24,
                      font=("Consolas", 13, "bold"),
                      fg_color=C["accent"], hover_color=C["accent2"],
                      text_color="#fff",
                      command=lambda n=name: self._inc(n)
                      ).pack(side="left", padx=1)

    def _inc(self, name: str):
        self._order[name]["qty"] += 1
        self._update_row(name)
        self._refresh_total()

    def _dec(self, name: str):
        self._order[name]["qty"] -= 1
        if self._order[name]["qty"] <= 0:
            self._order_rows[name].destroy()
            del self._order_rows[name]
            del self._order[name]
        else:
            self._update_row(name)
        self._refresh_total()

    # ── 金額計算 ─────────────────────────────────────────
    def _calc_discount(self, subtotal: int) -> int:
        raw = self._disc_var.get().strip()
        if not raw:
            return 0
        try:
            v = float(raw)
        except ValueError:
            return 0
        if self._disc_type.get() == "percent":
            return int(subtotal * v / 100)
        return int(v)

    def _refresh_total(self):
        subtotal = sum(i["price"] * i["qty"] for i in self._order.values())
        disc     = self._calc_discount(subtotal)
        total    = max(0, subtotal - disc)
        self._subtotal_lbl.configure(text=f"${subtotal}")
        self._total_lbl.configure(text=f"${total}")
        self._refresh_change()

    def _refresh_change(self):
        subtotal = sum(i["price"] * i["qty"] for i in self._order.values())
        disc     = self._calc_discount(subtotal)
        total    = max(0, subtotal - disc)
        try:
            recv   = int(self._recv_entry.get())
            change = recv - total
            color  = C["green"] if change >= 0 else C["red"]
            self._change_lbl.configure(text=f"${change}", text_color=color)
        except ValueError:
            self._change_lbl.configure(text="$—", text_color=C["green"])

    # ── 結帳 ─────────────────────────────────────────────
    def _checkout(self):
        if not self._order:
            messagebox.showwarning("訂單空白", "請先加入品項！")
            return

        subtotal = sum(i["price"] * i["qty"] for i in self._order.values())
        disc     = self._calc_discount(subtotal)
        total    = max(0, subtotal - disc)

        try:
            recv = int(self._recv_entry.get())
        except ValueError:
            messagebox.showwarning("收款金額", "請輸入收款金額！")
            return

        if recv < total:
            messagebox.showwarning("收款不足", f"收款 ${recv} 不足總計 ${total}！")
            return

        change = recv - total
        self._order_no += 1

        # 寫入紀錄
        self._write_record(subtotal, disc, total, recv, change)

        # 整理 order list
        order_list = [
            {"name": n, "price": d["price"], "qty": d["qty"], "note": d["note"]}
            for n, d in self._order.items()
        ]

        ReceiptWindow(self, order_list, disc, total, recv, change, self._order_no)

    # ── 寫入 TXT ─────────────────────────────────────────
    def _write_record(self, subtotal, disc, total, recv, change):
        today = datetime.now().strftime("%Y-%m-%d")
        path  = os.path.join(RECORD_DIR, f"{today}.txt")
        now   = datetime.now().strftime("%H:%M:%S")

        items_str = "、".join(
            f"{n}×{d['qty']}" + (f"({d['note']})" if d["note"] else "")
            for n, d in self._order.items()
        )
        disc_str = f" 折扣-{disc}" if disc else ""
        line = (f"[#{self._order_no:04d}] {now}  "
                f"{items_str}  "
                f"小計${subtotal}{disc_str}  總計${total}  "
                f"收款${recv}  找零${change}\n")

        with open(path, "a", encoding="utf-8") as f:
            f.write(line)

    # ── 清空 ─────────────────────────────────────────────
    def clear_order(self):
        for row in self._order_rows.values():
            row.destroy()
        self._order_rows.clear()
        self._order.clear()
        self._recv_entry.delete(0, "end")
        self._disc_var.set("")
        self._subtotal_lbl.configure(text="$0")
        self._total_lbl.configure(text="$0")
        self._change_lbl.configure(text="$—")


if __name__ == "__main__":
    app = POSApp()
    app.mainloop()