﻿# edited by glg
import sqlite3
from datetime import datetime

from pypos.core.base_controller import BaseController
from pypos.modules.penjualan.views.pembayaran_view import PembayaranView
from pypos.modules.penjualan.models.pembayaran_model import PembayaranModel
from pypos.core.utils.path_utils import get_db_path
from pypos.modules.penjualan.models.settlement_model import SettlementModel
from pypos.modules.penjualan.models.voucher_model import VoucherModel
from pypos.modules.penjualan.services.admin_authorization_service import AdminAuthorizationService
from pypos.modules.penjualan.services.penjualan_config_service import PenjualanConfigService
from pypos.modules.penjualan.services.pembayaran_calculation_service import PembayaranCalculationService
from pypos.modules.penjualan.services.pembayaran_result_service import PembayaranResultService
from pypos.modules.penjualan.services.pembayaran_voucher_service import PembayaranVoucherService
from pypos.modules.penjualan.services.pembayaran_admin_dialog_service import PembayaranAdminDialogService
from pypos.modules.penjualan.services.pembayaran_split_service import PembayaranSplitService
from pypos.modules.penjualan.services.pembayaran_mode_service import PembayaranModeService
from pypos.modules.penjualan.services.pembayaran_state_service import PembayaranStateService
from pypos.modules.penjualan.services.pembayaran_input_service import PembayaranInputService
from pypos.modules.penjualan.services.pembayaran_process_service import PembayaranProcessService
from pypos.modules.penjualan.services.pembayaran_form_service import PembayaranFormService
from pypos.modules.penjualan.services.transaksi_enterprise_control_service import (
    TransaksiEnterpriseControlService,
)

class PembayaranController(BaseController):

    def __init__(self, parent=None, info_transaksi=None, multi_payment_mode=False):
        super().__init__()
        self.result = None  # untuk menyimpan hasil
        self.multi_payment_mode = multi_payment_mode  # Flag untuk mode multi-payment
        self.state_service = PembayaranStateService()
        self.mode_service = PembayaranModeService()
        self.form_state = self.state_service.form_state
        self.db_path = get_db_path()
        self.model = PembayaranModel()
        self.voucher_model = VoucherModel(self.db_path)
        self.penjualan_config_service = PenjualanConfigService(db_path=self.db_path)
        self.calculation_service = PembayaranCalculationService()
        self.result_service = PembayaranResultService(db_path=self.db_path)
        self.input_service = PembayaranInputService()
        self.form_service = PembayaranFormService()
        self.voucher_service = PembayaranVoucherService(parse_rupiah_callback=self.parse_rupiah)
        self.split_service = PembayaranSplitService()
        self.process_service = PembayaranProcessService(
            split_service=self.split_service,
            state_service=self.state_service,
        )
        self.admin_authorization_service = AdminAuthorizationService(SettlementModel(self.db_path))
        self.admin_dialog_service = PembayaranAdminDialogService(
            validator=self.admin_authorization_service.validate_settlement_admin
        )
        self.enterprise_control_service = TransaksiEnterpriseControlService(db_path=self.db_path)

        if info_transaksi:

            self.model.info_transaksi = info_transaksi

        self.view = PembayaranView(self, parent, multi_payment_mode=multi_payment_mode)
        # edited by glg
        self._refresh_ppn_view_context()

        self.update_info_label()

        self.view.stacked_layout.setCurrentWidget(self.view.groupbox_tunai)

        self.show_tunai_form()
        self._setup_voucher_bindings()

    def _connect_signal_once(self, signal, slot):
        try:
            signal.disconnect(slot)
        except (AttributeError, RuntimeError, TypeError) as exc:
            self.log_debug(f"[PAYMENT_SIGNAL_DISCONNECT_SKIP] kemungkinan belum terpasang: {exc}")
        signal.connect(slot)

    def _setup_voucher_bindings(self):
        if getattr(self.view, "voucher_ui_locked", False):
            return
        if hasattr(self.view, "voucher_input"):
            self._connect_signal_once(self.view.voucher_input.textChanged, self._on_voucher_code_changed)
        if hasattr(self.view, "voucher_amount_input"):
            self._connect_signal_once(self.view.voucher_amount_input.textChanged, self._on_voucher_amount_changed)

    def _on_voucher_code_changed(self):
        if getattr(self.view, "voucher_ui_locked", False):
            return
        if self.state_service.is_voucher_updating():
            return
        code = self.view.voucher_input.text().strip()
        if not code:
            self._reset_voucher_state()
            return
        data = self.model.cek_voucher_valid(code)
        if not data:
            self._set_voucher_state(code, 0, 0, message="Voucher tidak valid / saldo habis")
            return
        saldo = float(data.get("saldo") or 0)
        # Default pakai min(saldo, total saat ini) jika amount kosong
        current_total = self._get_current_total_before_voucher()
        default_amount = min(saldo, current_total)
        self._set_voucher_state(code, saldo, default_amount)
        self._recalculate_all_totals()

    def _on_voucher_amount_changed(self):
        if self.state_service.is_voucher_updating():
            return
        code = self.voucher_service.get_code()
        if not code:
            return
        amount = self._parse_voucher_amount()
        clamped = self.voucher_service.clamp_amount_to_saldo(amount)
        if clamped < amount:
            # edited by glg
            # Guard wajib reset flag walau UI update / warning gagal.
            self.state_service.set_voucher_updating(True)
            try:
                self.view.voucher_amount_input.setText(self._format_voucher_amount_input(clamped))
                self.show_warning("Voucher", "Nominal voucher melebihi saldo.")
            finally:
                self.state_service.set_voucher_updating(False)
        self.voucher_service.set_state(code, self.voucher_service.get_saldo(), clamped)
        self._recalculate_all_totals()

    def _format_voucher_amount_input(self, amount: float) -> str:
        try:
            return str(int(round(float(amount or 0.0))))
        except (TypeError, ValueError):
            return "0"

    def _parse_voucher_amount(self) -> float:
        return self.voucher_service.parse_amount_text(self.view.voucher_amount_input.text())

    def _set_voucher_state(self, code, saldo, amount, message=None):
        self.voucher_service.set_state(code, saldo, amount)
        self.state_service.set_voucher_updating(True)
        try:
            if hasattr(self.view, "set_voucher_rows_visible"):
                locked = bool(getattr(self.view, "voucher_ui_locked", False))
                self.view.set_voucher_rows_visible(bool(code) and not locked)
            if hasattr(self.view, "voucher_saldo_label"):
                if message:
                    self.view.voucher_saldo_label.setText(message)
                else:
                    self.view.voucher_saldo_label.setText(self.view.format_rupiah(saldo))
            if hasattr(self.view, "voucher_amount_input"):
                self.view.voucher_amount_input.setText(self._format_voucher_amount_input(amount))
        finally:
            # edited by glg
            self.state_service.set_voucher_updating(False)

    def _reset_voucher_state(self):
        if hasattr(self.view, "set_voucher_rows_visible"):
            self.view.set_voucher_rows_visible(False)
        self._set_voucher_state("", 0, 0, message="-")
        self._recalculate_all_totals()

    def _get_active_mode(self):
        return self.mode_service.get_active_mode(self.view)

    def _get_mode_total_widget(self, mode):
        return self.mode_service.get_total_widget(self.view, mode)

    def _get_mode_ppn_widget(self, mode):
        return self.mode_service.get_ppn_widget(self.view, mode)

    def _get_mode_diskon_widget(self, mode):
        return self.mode_service.get_diskon_widget(self.view, mode)

    # edited by glg
    def _get_mode_diskon_nilai_widget(self, mode):
        return self.mode_service.get_diskon_nilai_widget(self.view, mode)

    def _get_mode_harus_dibayar_widget(self, mode):
        return self.mode_service.get_harus_dibayar_widget(self.view, mode)

    # edited by glg
    def _get_ppn_mode(self):
        return self.penjualan_config_service.get_ppn_mode(default="include")

    # edited by glg
    def _refresh_ppn_view_context(self):
        if not hasattr(self.view, "set_ppn_mode"):
            return
        self.view.set_ppn_mode(
            mode=self._get_ppn_mode(),
            percent=self.penjualan_config_service.get_ppn_percent(),
        )

    def _get_current_total_before_voucher(self) -> float:
        mode = self._get_active_mode()
        try:
            total_widget = self._get_mode_total_widget(mode)
            if total_widget is not None:
                return float(self.parse_rupiah(total_widget.text()))
        except (AttributeError, RuntimeError, TypeError, ValueError) as e:
            self.log_warning(f"[PAYMENT_TOTAL_BEFORE_VOUCHER_ERROR] {e}")
        return float(self.model.info_transaksi.total_belanja or 0)

    def _apply_voucher_to_total(self, total_nilai: float) -> float:
        if getattr(self.view, "voucher_ui_locked", False):
            return total_nilai
        total_after, _, clamped = self.voucher_service.apply_to_total(total_nilai)
        if clamped != self.voucher_service.get_amount():
            self.state_service.set_voucher_updating(True)
            self.voucher_service.set_state(
                self.voucher_service.get_code(),
                self.voucher_service.get_saldo(),
                clamped,
            )
            self.view.voucher_amount_input.setText(self._format_voucher_amount_input(clamped))
            self.state_service.set_voucher_updating(False)
        return total_after

    def _recalculate_all_totals(self):
        mode = self._get_active_mode()
        if mode == "tunai":
            self.hitung_total_harus_dibayar()
        elif mode == "credit":
            self.hitung_total_harus_dibayar_credit()
        else:
            self.hitung_total_harus_dibayar_debit()

    def get_payment_result(self):
        view = self.view
        input_bayar_tunai = getattr(view, "input_bayar_tunai", None)
        input_harus_bayar_tunai = getattr(view, "input_total_harus_dibayar_tunai", None)
        if input_bayar_tunai is None or input_harus_bayar_tunai is None:
            self.log_warning("input_bayar_tunai belum ada pada view pembayaran.")
        else:
            self.log_debug(f"input bayar: {input_bayar_tunai.text()}")
            self.log_debug(f"harus bayar: {input_harus_bayar_tunai.text()}")

        mode = self._get_active_mode()
        try:
            result_mode, payload = self.input_service.build_result_payload(
                mode=mode,
                view=self.view,
                parse_rupiah_callback=self.parse_rupiah,
                selected_text_callback=self._get_selected_button_text,
            )
            # edited by glg
            # Saat diskon nominal dipilih, persentase disetarakan dari nominal/total awal.
            if self.state_service.get_diskon_source(mode, "percent") == "nominal":
                total_widget = self._get_mode_total_widget(mode)
                total_awal = 0.0
                if total_widget is not None:
                    total_awal = float(self.parse_rupiah(total_widget.text()))
                diskon_nilai = float(payload.get("diskon_nilai") or 0.0)
                diskon_persen = (diskon_nilai / total_awal * 100.0) if total_awal > 0 else 0.0
                payload["diskon_persen"] = max(0.0, diskon_persen)
            vcode, vamount = self._consume_voucher_for_result()
            payload["voucher_code"] = vcode
            payload["voucher_amount"] = vamount
            return self.result_service.create_result(mode=result_mode, **payload)
        except (AttributeError, RuntimeError, TypeError, ValueError, KeyError) as e:
            self.log_error(f"[PAYMENT_RESULT_VALIDATION_ERROR] mode={mode} err={e}")
            return None

    def _get_selected_button_text(self, buttons):
        return self.mode_service.get_selected_button_text(buttons)

    def show(self):
        return self.view.exec()

    def parse_rupiah(self, text: str) -> float:
        """Mengubah string rupiah lokal ke float Python"""
        return self.view.parse_rupiah(text)

    def update_info_label(self):
        info = self.model.info_transaksi
        if not info:
            self.log_warning("Info transaksi kosong saat update_info_label.")
            return
        self.view.label_tanggal.setText(str(getattr(info, "tanggal", "")))
        self.view.label_jenis.setText(str(getattr(info, "jenis_item", 0)))
        self.view.label_qty.setText(str(getattr(info, "total_qty", 0)))
        total_belanja = float(getattr(info, "total_belanja", 0) or 0)
        self.view.label_total.setText(f"{total_belanja:,.0f}")

    def cek_radiobutton(self, mode: str):
        self.log_debug(f"Cek radiobutton untuk mode: {mode}")

        if mode == "tunai":

            self.show_tunai_form()

        elif mode == "credit":

            self.show_credit_form()

        elif mode == "debit":

            self.show_debit_form()

    def _set_breakdown_fields(self, mode: str, diskon_nilai: float, voucher_nilai: float, sync_nominal: bool = True):
        suffix = {"tunai": "tunai", "credit": "credit", "debit": "debit"}.get(mode)
        if not suffix:
            return

        diskon_widget = getattr(self.view, f"diskon_tambahan_rp_{suffix}", None)
        voucher_widget = getattr(self.view, f"potongan_voucher_{suffix}", None)

        if diskon_widget is not None and bool(sync_nominal):
            self.state_service.set_diskon_field_syncing(True)
            try:
                diskon_widget.setText(self.view.format_rupiah(max(0.0, float(diskon_nilai or 0))))
            finally:
                self.state_service.set_diskon_field_syncing(False)
        if voucher_widget is not None:
            voucher_widget.setText(self.view.format_rupiah(max(0.0, float(voucher_nilai or 0))))

    # edited by glg
    def _on_diskon_percent_changed(self, mode: str):
        if self.state_service.is_diskon_field_syncing():
            return
        self.state_service.set_diskon_source(mode, "percent")
        self._reset_verifikasi_diskon()
        self._hitung_total_harus_dibayar_by_mode(mode)

    # edited by glg
    def _on_diskon_nominal_changed(self, mode: str):
        if self.state_service.is_diskon_field_syncing():
            return
        self.state_service.set_diskon_source(mode, "nominal")
        self._reset_verifikasi_diskon()
        self._hitung_total_harus_dibayar_by_mode(mode)

    # edited by glg
    def _on_diskon_percent_tunai_changed(self, *_args):
        self._on_diskon_percent_changed("tunai")

    # edited by glg
    def _on_diskon_nominal_tunai_changed(self, *_args):
        self._on_diskon_nominal_changed("tunai")

    # edited by glg
    def _on_diskon_percent_credit_changed(self, *_args):
        self._on_diskon_percent_changed("credit")

    # edited by glg
    def _on_diskon_nominal_credit_changed(self, *_args):
        self._on_diskon_nominal_changed("credit")

    # edited by glg
    def _on_diskon_percent_debit_changed(self, *_args):
        self._on_diskon_percent_changed("debit")

    # edited by glg
    def _on_diskon_nominal_debit_changed(self, *_args):
        self._on_diskon_nominal_changed("debit")

    def show_credit_form(self):
        self.log_debug("masuk ke show_credit_form")
        self._refresh_ppn_view_context()
        ppn_value = self.penjualan_config_service.get_ppn_percent()
        self.form_service.show_credit_form(
            view=self.view,
            total_awal=float(self.model.info_transaksi.total_belanja or 0),
            ppn_value=ppn_value,
            connect_signal_once=self._connect_signal_once,
            recalculate_callback=self.hitung_total_harus_dibayar_credit,
            reset_verifikasi_callback=self._reset_verifikasi_diskon,
            on_percent_changed_callback=self._on_diskon_percent_credit_changed,
            on_nominal_changed_callback=self._on_diskon_nominal_credit_changed,
        )
        self.log_debug(f"nilai ppn = {ppn_value} mode={self._get_ppn_mode()}")

    def show_debit_form(self):
        self.log_debug("masuk ke show_debit_form")
        self._refresh_ppn_view_context()
        self.form_service.show_debit_form(
            view=self.view,
            total_awal=float(self.model.info_transaksi.total_belanja or 0),
            ppn_value=self.penjualan_config_service.get_ppn_percent(),
            connect_signal_once=self._connect_signal_once,
            recalculate_callback=self.hitung_total_harus_dibayar_debit,
            reset_verifikasi_callback=self._reset_verifikasi_diskon,
            on_percent_changed_callback=self._on_diskon_percent_debit_changed,
            on_nominal_changed_callback=self._on_diskon_nominal_debit_changed,
        )

    def show_tunai_form(self):
        self.log_debug("masuk ke show_tunai_form")
        self._refresh_ppn_view_context()
        self.form_service.show_tunai_form(
            view=self.view,
            total_awal=float(self.model.info_transaksi.total_belanja or 0),
            ppn_value=self.penjualan_config_service.get_ppn_percent(),
            connect_signal_once=self._connect_signal_once,
            recalculate_callback=self.hitung_total_harus_dibayar,
            reset_verifikasi_callback=self._reset_verifikasi_diskon,
            hitung_kembalian_callback=self.hitung_kembalian_otomatis,
            on_percent_changed_callback=self._on_diskon_percent_tunai_changed,
            on_nominal_changed_callback=self._on_diskon_nominal_tunai_changed,
        )

    def _hitung_total_harus_dibayar_by_mode(self, mode):
        self.form_service.calculate_total_harus_dibayar_by_mode(
            mode=mode,
            view=self.view,
            mode_service=self.mode_service,
            parse_rupiah_callback=self.parse_rupiah,
            calculation_service=self.calculation_service,
            ppn_percent=self.penjualan_config_service.get_ppn_percent(),
            ppn_mode=self._get_ppn_mode(),
            apply_voucher_callback=self._apply_voucher_to_total,
            set_breakdown_callback=self._set_breakdown_fields,
            get_diskon_source_callback=self.state_service.get_diskon_source,
            log_debug_callback=self.log_debug,
            log_error_callback=self.log_error,
        )

    def hitung_total_harus_dibayar_debit(self):
        self._hitung_total_harus_dibayar_by_mode("debit")

    def hitung_total_harus_dibayar_credit(self):
        self._hitung_total_harus_dibayar_by_mode("credit")

    def hitung_total_harus_dibayar(self):
        self._hitung_total_harus_dibayar_by_mode("tunai")
        self.hitung_kembalian_otomatis()

    def hitung_kembalian_otomatis(self):
        try:
            bayar = self.parse_rupiah(self.view.input_bayar_tunai.text())
            harus_bayar = self.parse_rupiah(self.view.input_total_harus_dibayar_tunai.text())
            # print(f"input bayar: {bayar}")
            # print(f"harus bayar: {harus_bayar}")
            kembali = bayar - harus_bayar
            self.view.input_kembalian_tunai.setText(self.view.format_rupiah(kembali))
        except (AttributeError, RuntimeError, TypeError, ValueError) as e:
            self.log_error(f"[PAYMENT_CHANGE_CALC_ERROR] {e}")
            self.view.input_kembalian_tunai.setText("0")

    def _reset_verifikasi_diskon(self):
        self.state_service.reset_diskon_admin()

    def _get_diskon_value(self) -> int:
        mode = self._get_active_mode()
        diskon_widget = self._get_mode_diskon_widget(mode)
        if diskon_widget is not None:
            return int(diskon_widget.value())
        return 0

    def _verifikasi_admin_diskon(self) -> bool:
        approval_name = self.admin_dialog_service.verify(
            parent=self.view,
            warn_callback=lambda title, message, dialog: self.show_warning(title, message, view=dialog),
            return_admin_name=True,
        )
        approved = bool(str(approval_name or "").strip())
        actor_name = ""
        try:
            info_transaksi = getattr(self.model, "info_transaksi", None)
            actor_name = str(getattr(info_transaksi, "kasir", "") or "").strip()
        except (AttributeError, RuntimeError, TypeError):
            actor_name = ""
        try:
            self.enterprise_control_service.record_approval_trail(
                action_name="diskon_pembayaran",
                actor_name=actor_name,
                approval_name=str(approval_name or ""),
                approval_status="approved" if approved else "rejected",
                trace_id=f"pay-auth-{datetime.now().strftime('%H%M%S%f')[-8:]}",
                payload={
                    "mode": str(self._get_active_mode() or ""),
                    "diskon_value": int(self._get_diskon_value() or 0),
                },
            )
        except (sqlite3.Error, RuntimeError, TypeError, ValueError) as exc:
            self.log_warning(f"Gagal catat approval trail diskon: {exc}")
        return approved

    def process_payment(self):
        if not self._validate_voucher_before_payment():
            return
        mode = self._get_active_mode()
        flow_result = self.process_service.process(
            mode=mode,
            diskon_value=self._get_diskon_value(),
            read_amounts=lambda active_mode: self.split_service.read_amounts(active_mode, self.view, self.parse_rupiah),
            verify_admin=self._verifikasi_admin_diskon,
            ask_confirm=self.ask_confirm,
            show_warning=self.show_warning,
            format_rupiah=self.view.format_rupiah,
            create_payment_result=self.create_payment_result,
            get_payment_result=self.get_payment_result,
            prepare_split_payment_ui=self.prepare_split_payment_ui,
        )
        if flow_result.get("status") == "ok":
            self.result = flow_result.get("result")
            self.view.accept()
        elif flow_result.get("status") == "invalid":
            self.show_warning("Pembayaran Gagal", "Input belum lengkap atau invalid.")

    def create_payment_result(self):
        """Helper untuk membuat object PaymentResult dari state saat ini"""
        return self.get_payment_result()

    def _consume_voucher_for_result(self):
        return self.voucher_service.consume_for_result()

    def _validate_voucher_before_payment(self) -> bool:
        is_valid, message, saldo = self.voucher_service.validate_before_payment(
            voucher_ui_locked=bool(getattr(self.view, "voucher_ui_locked", False)),
            cek_voucher_callback=self.model.cek_voucher_valid,
        )
        if not is_valid:
            self.show_warning("Voucher", message)
            return False
        if saldo is not None and self.voucher_service.get_code():
            clamped = self.voucher_service.clamp_amount_to_saldo(self.voucher_service.get_amount())
            self.voucher_service.set_state(self.voucher_service.get_code(), saldo, clamped)
            if hasattr(self.view, "voucher_saldo_label"):
                self.view.voucher_saldo_label.setText(self.view.format_rupiah(saldo))
            if hasattr(self.view, "voucher_amount_input"):
                # edited by glg
                self.state_service.set_voucher_updating(True)
                try:
                    self.view.voucher_amount_input.setText(self._format_voucher_amount_input(clamped))
                finally:
                    self.state_service.set_voucher_updating(False)
        return True

    def prepare_split_payment_ui(self):
        self.view.input_total_tunai.setEnabled(False)
        self.view.input_bayar_tunai.setEnabled(False)
        self.view.radio_tunai.setEnabled(False)
        remaining_str = self.view.format_rupiah(self.split_service.get_remaining_amount())
        self.view.radio_credit.setChecked(True)
        self.view.input_total_credit.setText(remaining_str)
        self.view.input_total_harus_dibayar_credit.setText(remaining_str)
        self.view.input_total_debit.setText(remaining_str)
        self.view.input_total_harus_dibayar_debit.setText(remaining_str)
        self.view.setWindowTitle(f"Pembayaran Sisa: {remaining_str}")



