import re
import time

from PySide6.QtCore import Qt, Signal, QTimer
from PySide6.QtGui import QIntValidator
from PySide6.QtWidgets import (
    QComboBox,
    QDialog,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMessageBox,
    QPushButton,
    QSizePolicy,
    QStyledItemDelegate,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QWidget,
    QHeaderView,
)

from pypos.core.base_view import BaseView
from pypos.core.utils.db_helper import parse_int_from_text, parse_int_safely
from pypos.core.utils.worker_pool_utils import submit_ui_query_task_keyed


class ReturnView(BaseView, QWidget):
    voucher_terbit = Signal(str)
    master_payload_ready = Signal(int, object)
    detail_payload_ready = Signal(int, object)

    def __init__(self, controller, parent=None):
        super().__init__(parent)
        self.controller = controller
        if hasattr(self.controller, "set_view"):
            self.controller.set_view(self)

        self.setWindowTitle("Return Penjualan")
        # edited by glg
        # Return page dipakai sebagai widget layout, jadi hindari resize statis.
        self.setMinimumSize(760, 460)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.transaksi_id_selected = None
        self._updating_qty = False
        # edited by glg
        # Debounce pencarian agar query master return tidak dipanggil di setiap karakter.
        self._search_debounce_timer = QTimer(self)
        self._search_debounce_timer.setSingleShot(True)
        self._search_debounce_timer.setInterval(280)
        self._search_debounce_timer.timeout.connect(self.refresh_master)
        # edited by glg
        # Cache refresh master agar query identik tidak dieksekusi berulang
        # saat user berpindah tab/menu dengan cepat.
        self._master_refresh_cache_key = ""
        self._master_refresh_cache_ts_ms = 0.0
        self._master_refresh_cache_ttl_ms = 600
        self._detail_refresh_cache_key = ""
        self._master_refresh_request_seq = 0
        self._detail_request_seq = 0
        self.master_payload_ready.connect(self._on_master_payload_ready, Qt.QueuedConnection)
        self.detail_payload_ready.connect(self._on_detail_payload_ready, Qt.QueuedConnection)

        vbox = QVBoxLayout(self)

        top_bar = QHBoxLayout()
        self.search_edit = QLineEdit()
        self.search_edit.setPlaceholderText("Cari nomor / tanggal / customer...")
        self.search_edit.textChanged.connect(self._schedule_refresh_master)
        self.cb_refund = QComboBox()
        self.cb_refund.addItems(["Cash", "Voucher"])
        self.cb_refund.currentTextChanged.connect(self._on_refund_method_changed)
        top_bar.addWidget(QLabel("Metode Refund:"))
        top_bar.addWidget(self.cb_refund)
        top_bar.addWidget(self.search_edit)
        vbox.addLayout(top_bar)

        self.info_batas = QLabel("")
        vbox.addWidget(self.info_batas)

        self.tbl_master = QTableWidget(0, 4)
        self.tbl_master.setHorizontalHeaderLabels(["Nomer", "Tanggal", "Customer", "Total"])
        self.tbl_master.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tbl_master.setSelectionBehavior(QTableWidget.SelectRows)
        self.tbl_master.setEditTriggers(QTableWidget.NoEditTriggers)
        self.tbl_master.cellClicked.connect(self.master_clicked)
        vbox.addWidget(self.tbl_master)

        self.tbl_detail = QTableWidget(0, 7)
        self.tbl_detail.setHorizontalHeaderLabels(
            ["Id Produk", "Produk", "Harga", "Qty Jual", "Qty Return", "Subtotal", "Jenis"]
        )

        header = self.tbl_detail.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        for i in range(1, 7):
            header.setSectionResizeMode(i, QHeaderView.Stretch)

        self.tbl_detail.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.SelectedClicked)
        self.tbl_detail.itemChanged.connect(self._on_qty_return_changed)
        self.tbl_detail.setItemDelegateForColumn(4, _QtyReturnDelegate(self.tbl_detail))
        vbox.addWidget(self.tbl_detail)

        self.btn_return = QPushButton("Proses Return Tunai")
        self.btn_return.setProperty("class", "return-btn")
        self.btn_return.setEnabled(False)
        self.btn_return.clicked.connect(self.proses_return)
        vbox.addWidget(self.btn_return)

        self.refresh_master(force=True)

    def _on_refund_method_changed(self, value):
        label = "Proses Return Tunai" if str(value).lower() == "cash" else "Proses Return Voucher"
        self.btn_return.setText(label)

    def refresh_master(self, force=False):
        start_date, end_date = self.controller.get_allowed_date_range()
        keyword = str(self.search_edit.text() or "")
        cache_key = f"{keyword.strip().lower()}|{start_date.strftime('%Y-%m-%d')}|{end_date.strftime('%Y-%m-%d')}"
        now_ms = float(time.monotonic() * 1000.0)
        if (
            not bool(force)
            and cache_key == str(self._master_refresh_cache_key or "")
            and (now_ms - float(self._master_refresh_cache_ts_ms or 0.0)) < float(self._master_refresh_cache_ttl_ms)
        ):
            return

        if bool(force):
            payload = self._build_master_refresh_payload(keyword, start_date, end_date)
            payload["cache_key"] = cache_key
            payload["cache_ts_ms"] = now_ms
            self._apply_master_refresh_payload(payload)
            return

        request_id = self._next_master_refresh_request_id()

        def _worker():
            started_at = time.perf_counter()
            payload = self._build_master_refresh_payload(keyword, start_date, end_date)
            payload["cache_key"] = cache_key
            payload["cache_ts_ms"] = now_ms
            payload["elapsed_ms"] = (time.perf_counter() - started_at) * 1000.0
            self.master_payload_ready.emit(int(request_id), payload)

        submit_ui_query_task_keyed(f"return-master:{id(self)}", _worker)

    # edited by glg
    def _next_master_refresh_request_id(self):
        self._master_refresh_request_seq += 1
        return int(self._master_refresh_request_seq)

    # edited by glg
    def _build_master_refresh_payload(self, keyword, start_date, end_date):
        return {
            "rows": list(self.controller.search_transaksi_master(keyword) or []),
            "start_date_text": start_date.strftime("%Y-%m-%d"),
            "end_date_text": end_date.strftime("%Y-%m-%d"),
        }

    # edited by glg
    def _on_master_payload_ready(self, request_id, payload):
        if int(request_id or 0) != int(self._master_refresh_request_seq or 0):
            return
        data = payload if isinstance(payload, dict) else {}
        self._apply_master_refresh_payload(data)
        elapsed_ms = float(data.get("elapsed_ms") or 0.0)
        if elapsed_ms >= 220.0:
            self.log_info(f"[PERF] return_master_refresh_async elapsed_ms={elapsed_ms:.1f}")

    # edited by glg
    def _apply_master_refresh_payload(self, payload):
        data = payload if isinstance(payload, dict) else {}
        start_date_text = str(data.get("start_date_text") or "")
        end_date_text = str(data.get("end_date_text") or "")
        rows = data.get("rows") or []
        self.info_batas.setText(
            f"Hanya transaksi tanggal {start_date_text} s/d {end_date_text} yang bisa direturn."
        )
        self.tbl_master.setUpdatesEnabled(False)
        try:
            self.tbl_master.setRowCount(len(rows))
            for r, row in enumerate(rows):
                self.tbl_master.setItem(r, 0, QTableWidgetItem(str(row["id"])))
                tanggal = row.get("tanggal") if hasattr(row, "get") else row["tanggal"]
                if tanggal is None and "dtime" in row.keys():
                    tanggal = row["dtime"]
                self.tbl_master.setItem(r, 1, QTableWidgetItem(str(tanggal)))
                self.tbl_master.setItem(r, 2, QTableWidgetItem(row["customers_nama"]))
                self.tbl_master.setItem(r, 3, QTableWidgetItem(f"{row['transaksi_nilai']:,.0f}"))
        finally:
            self.tbl_master.setUpdatesEnabled(True)

        self.tbl_detail.setRowCount(0)
        self.btn_return.setEnabled(False)
        self.transaksi_id_selected = None
        self._detail_refresh_cache_key = ""
        self._master_refresh_cache_key = str(data.get("cache_key") or "")
        self._master_refresh_cache_ts_ms = float(data.get("cache_ts_ms") or 0.0)

    # edited by glg
    def _schedule_refresh_master(self, _text=""):
        if hasattr(self, "_search_debounce_timer") and self._search_debounce_timer is not None:
            self._search_debounce_timer.start()

    def master_clicked(self, row, _col):
        item_id = self.tbl_master.item(row, 0)
        nomer = item_id.text() if item_id else ""
        if not nomer:
            return
        if nomer == str(self._detail_refresh_cache_key or "") and self.tbl_detail.rowCount() > 0:
            self.transaksi_id_selected = nomer
            return
        self.transaksi_id_selected = nomer
        request_id = self._next_detail_request_id()

        def _worker():
            started_at = time.perf_counter()
            data = self.controller.load_transaksi_detail(nomer)
            self.detail_payload_ready.emit(
                int(request_id),
                {
                    "nomer": str(nomer),
                    "rows": list(data or []),
                    "elapsed_ms": (time.perf_counter() - started_at) * 1000.0,
                },
            )

        submit_ui_query_task_keyed(f"return-detail:{id(self)}", _worker)

    # edited by glg
    def _next_detail_request_id(self):
        self._detail_request_seq += 1
        return int(self._detail_request_seq)

    # edited by glg
    def _on_detail_payload_ready(self, request_id, payload):
        if int(request_id or 0) != int(self._detail_request_seq or 0):
            return
        data = payload if isinstance(payload, dict) else {}
        nomer = str(data.get("nomer") or "")
        if nomer and nomer != str(self.transaksi_id_selected or ""):
            return
        self._detail_refresh_cache_key = nomer
        self.populate_detail(data.get("rows") or [])
        elapsed_ms = float(data.get("elapsed_ms") or 0.0)
        if elapsed_ms >= 180.0:
            self.log_info(f"[PERF] return_detail_refresh_async elapsed_ms={elapsed_ms:.1f}")

    def populate_detail(self, data):
        self._updating_qty = True
        self.tbl_detail.blockSignals(True)
        self.tbl_detail.setUpdatesEnabled(False)
        try:
            self.tbl_detail.setRowCount(len(data))
            for r, d in enumerate(data):
                def _make_readonly_item(text):
                    item = QTableWidgetItem(str(text))
                    item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                    return item

                self.tbl_detail.setItem(r, 0, _make_readonly_item(d["produk_id"]))
                self.tbl_detail.setItem(r, 1, _make_readonly_item(d["produk_nama"]))
                self.tbl_detail.setItem(r, 2, _make_readonly_item(f"{d['produk_ord_hrg']:,.0f}"))
                qty_jual = d.get("produk_ord_jml")
                qty_returnable = d.get("qty_returnable", qty_jual)
                self.tbl_detail.setItem(r, 3, _make_readonly_item(qty_returnable))

                qty_item = QTableWidgetItem("")
                qty_item.setFlags(qty_item.flags() | Qt.ItemIsEditable)
                qty_item.setData(Qt.UserRole, int(qty_returnable or 0))
                self.tbl_detail.setItem(r, 4, qty_item)

                self.tbl_detail.setItem(r, 5, _make_readonly_item("0"))
                self.tbl_detail.setItem(r, 6, _make_readonly_item("partial"))
        finally:
            self.tbl_detail.blockSignals(False)
            self.tbl_detail.setUpdatesEnabled(True)
            self._updating_qty = False

        self.calculate_subtotal()

    def calculate_subtotal(self):
        def _set_readonly_subtotal(row_index, value_text):
            subtotal_item = QTableWidgetItem(value_text)
            subtotal_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self.tbl_detail.setItem(row_index, 5, subtotal_item)

        total = 0
        for r in range(self.tbl_detail.rowCount()):
            qty_cell = self.tbl_detail.item(r, 4)
            harga_cell = self.tbl_detail.item(r, 2)
            qty = parse_int_from_text(qty_cell.text() if qty_cell else "")
            harga = parse_int_safely(harga_cell.text() if harga_cell else "0")
            if qty > 0:
                subtotal = qty * harga
                _set_readonly_subtotal(r, f"{subtotal:,.0f}")
                total += subtotal
            else:
                _set_readonly_subtotal(r, "0")

        self.btn_return.setEnabled(total > 0)

    def _on_qty_return_changed(self, item):
        if self._updating_qty:
            return
        if item is None or item.column() != 4:
            return

        try:
            max_qty = item.data(Qt.UserRole)
            if max_qty is None:
                max_qty = 0

            raw_text = str(item.text() or "").strip()
            qty_val = 0
            if raw_text and not re.fullmatch(r"\d+", raw_text):
                self._updating_qty = True
                item.setText("")
                self.show_warning("Validasi", "Qty return hanya boleh angka.")
            else:
                qty_val = parse_int_from_text(raw_text)

            if qty_val > int(max_qty):
                self._updating_qty = True
                item.setText(str(max_qty))
                self.show_warning("Validasi", f"Qty return tidak boleh melebihi Qty Jual ({max_qty}).")
        finally:
            self._updating_qty = False

        self.calculate_subtotal()

    def _build_confirm_message(self, item_names):
        names = [str(name or "").strip() for name in item_names if str(name or "").strip()]
        if not names:
            return "Apakah Anda yakin akan membatalkan produk yang dipilih?"
        if len(names) <= 5:
            lines = "\n".join([f"- {name}" for name in names])
        else:
            lines = "\n".join([f"- {name}" for name in names[:5]])
            lines += f"\n- dan {len(names) - 5} produk lainnya"
        return "Apakah Anda yakin akan membatalkan produk berikut?\n" + lines

    def _show_return_confirmation(self, item_names):
        msg = QMessageBox(self)
        msg.setWindowTitle("Konfirmasi Return")
        msg.setIcon(QMessageBox.Question)
        msg.setText(self._build_confirm_message(item_names))
        btn_confirm = msg.addButton("Konfirmasi", QMessageBox.AcceptRole)
        msg.addButton("Batal", QMessageBox.RejectRole)
        msg.exec()
        return msg.clickedButton() == btn_confirm

    def _verifikasi_admin_return(self, parent=None):
        dialog = QDialog(parent or self)
        dialog.setWindowTitle("Verifikasi Admin")
        layout = QVBoxLayout(dialog)

        layout.addWidget(QLabel("Admin:"))
        admin_input = QLineEdit()
        admin_input.setPlaceholderText("Masukkan username admin (nama_login)")
        layout.addWidget(admin_input)

        layout.addWidget(QLabel("Password:"))
        password_input = QLineEdit()
        password_input.setEchoMode(QLineEdit.Password)
        password_input.setPlaceholderText("Masukkan password admin")
        layout.addWidget(password_input)

        button_layout = QHBoxLayout()
        cancel_button = QPushButton("BATAL")
        lanjut_button = QPushButton("LANJUTKAN")
        button_layout.addStretch()
        button_layout.addWidget(cancel_button)
        button_layout.addWidget(lanjut_button)
        layout.addLayout(button_layout)

        cancel_button.clicked.connect(dialog.reject)

        def handle_lanjutkan():
            admin_name = admin_input.text().strip()
            password = password_input.text()
            is_valid, message = self.controller.verify_admin_authorization(admin_name, password)
            if not is_valid:
                self.show_warning("Validasi", message)
                return
            dialog.accept()

        lanjut_button.clicked.connect(handle_lanjutkan)
        lanjut_button.setDefault(True)
        lanjut_button.setAutoDefault(True)

        if dialog.exec() == QDialog.Accepted:
            return admin_input.text().strip()
        return None

    def _collect_selected_items(self):
        selected_items = []
        total_rows = self.tbl_detail.rowCount()
        full_item_count = 0

        for r in range(total_rows):
            qty_item = self.tbl_detail.item(r, 4)
            if qty_item is None:
                continue

            raw_qty = str(qty_item.text() or "").strip()
            if raw_qty and not re.fullmatch(r"\d+", raw_qty):
                return {"ok": False, "message": "Qty return hanya boleh angka."}

            qty_return = parse_int_from_text(raw_qty)
            if qty_return <= 0:
                continue

            max_qty = parse_int_from_text(qty_item.data(Qt.UserRole))
            if max_qty > 0 and qty_return > max_qty:
                return {
                    "ok": False,
                    "message": f"Qty return untuk baris {r + 1} melebihi Qty Jual ({max_qty}).",
                }

            produk_id_item = self.tbl_detail.item(r, 0)
            produk_nama_item = self.tbl_detail.item(r, 1)
            harga_item = self.tbl_detail.item(r, 2)
            jenis_item = self.tbl_detail.item(r, 6)
            qty_jual_item = self.tbl_detail.item(r, 3)

            selected_items.append(
                {
                    "produk_id": produk_id_item.text() if produk_id_item else "",
                    "produk_nama": produk_nama_item.text() if produk_nama_item else "",
                    "qty_return": qty_return,
                    "harga": parse_int_safely(harga_item.text()) if harga_item else 0,
                    "jenis_return": jenis_item.text() if jenis_item else "partial",
                }
            )

            qty_jual = parse_int_from_text(qty_jual_item.text() if qty_jual_item else "0")
            if qty_jual > 0 and qty_return >= qty_jual:
                full_item_count += 1

        if not selected_items:
            return {"ok": False, "message": "Belum ada qty return diisi."}

        if total_rows > 0 and full_item_count == total_rows:
            return {
                "ok": False,
                "message": "Return penuh satu nota tidak diizinkan.\nGunakan tab Pembatalan Transaksi untuk membatalkan satu nota penuh.",
            }

        return {"ok": True, "items": selected_items}

    def proses_return(self):
        refund_method = str(self.cb_refund.currentText() or "cash").lower()
        if not self.transaksi_id_selected:
            self.show_warning("Validasi", "Pilih transaksi terlebih dahulu.")
            return

        selected = self._collect_selected_items()
        if not selected.get("ok"):
            self.show_warning("Validasi", selected.get("message") or "Data return tidak valid.")
            return

        selected_items = selected.get("items") or []
        item_names = [item.get("produk_nama") for item in selected_items]
        if not self._show_return_confirmation(item_names):
            return

        admin_name = self._verifikasi_admin_return(self)
        if not admin_name:
            return

        result = self.controller.proses_return(
            transaksi_id=self.transaksi_id_selected,
            selected_items=selected_items,
            refund_method=refund_method,
            jenis_return="partial",
        )
        if not result.get("ok"):
            self.show_error("Return", result.get("message") or "Gagal membuat return.")
            return

        kode_return = result.get("kode_return") or ""
        total_refund = float(result.get("total_refund") or 0)
        refund_method = str(result.get("refund_method") or refund_method).lower()
        label_method = "voucher" if refund_method == "voucher" else "tunai"
        title = "Return Voucher" if refund_method == "voucher" else "Return Tunai"
        self.show_info(title, f"Return {label_method} berhasil.\nKode Return: {kode_return}\nTotal Refund: Rp {total_refund:,.0f}")

        self.refresh_master(force=True)
        self.voucher_terbit.emit(kode_return)

        printed = self.controller.cetak_voucher_return(kode_return, refund_method, parent=self)
        if refund_method == "voucher" and not printed:
            self.show_warning("Printer", "Gagal mencetak voucher. Periksa setting printer.")


class _QtyReturnDelegate(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = QLineEdit(parent)
        editor.setValidator(QIntValidator(0, 1000000, editor))
        return editor
