from PySide6.QtPrintSupport import QPrinter
from PySide6.QtGui import QPainter
from typing import Optional
from pypos.core.base_controller import BaseController
from pypos.modules.printer.views.print_view import PrintView
from pypos.modules.printer.config.printer_config import DEFAULT_PAPER_SIZE, THERMAL_DPI
from pypos.modules.printer.services.settlement_print_service import SettlementPrintService
from pypos.modules.printer.services.print_profile_service import PrintProfileService

class PrintController(BaseController):
    def __init__(self, printer_settings_controller):
        super().__init__()
        self.printer_settings_controller = printer_settings_controller
        self.print_view = self.bind_view(PrintView(), bind_back=False)
        self.print_profile_service = PrintProfileService()
        self.settlement_print_service = SettlementPrintService(
            print_profile_service=self.print_profile_service,
            log_info=self.log_info,
            log_warning=self.log_warning,
            log_error=self.log_error,
        )

    def _resolve_printer_config(self, index_printer: Optional[int]):
        return self.print_profile_service.resolve_printer_config(
            self.printer_settings_controller,
            index_printer=index_printer,
        )

    def test_printer_width(self, index_printer: int = 0):
        """
        Test actual character width printer untuk deteksi CPL.
        Print test pattern dari 25-50 chars untuk menemukan batas terpotong.
        """
        cfg = self._resolve_printer_config(index_printer)
        if not cfg:
            self.log_warning("Tidak ada printer terpilih.")
            return False
        printer_name = cfg.get("name", "")

        self.log_info(f"Testing printer width: {printer_name}")
        return self.printer_settings_controller.test_printer_width(printer_name)

    def print_struk(self, transaksi_data, detail_data, transaksi_data_dict, index_printer: Optional[int] = None, copy_no: int = 0):
        cfg = self._resolve_printer_config(index_printer)
        if not cfg:
            self.log_warning("Tidak ada printer terpilih.")
            return False
        printer_name = cfg.get("name", "")
        paper_label = cfg.get("paper_size", DEFAULT_PAPER_SIZE)

        items, payment, wifi_code, qr_data, setting = self._prepare_print_data(transaksi_data, detail_data, transaksi_data_dict)

        # Try ESC/POS RAW printing first (solusi untuk text terpotong)
        self.log_info("Trying ESC/POS RAW printing...")
        escpos_success = self.printer_settings_controller.print_escpos_raw(
            items, payment, wifi_code, qr_data, setting, transaksi_data_dict, printer_name
        )

        if escpos_success:
            return True

        # Fallback ke QPainter jika ESC/POS gagal
        self.log_warning("ESC/POS failed, fallback to QPainter...")

        printer = QPrinter(QPrinter.HighResolution)
        printer.setPrinterName(printer_name)
        printer.setResolution(THERMAL_DPI)
        printer.setOutputFormat(QPrinter.NativeFormat)
        printer.setFullPage(True)
        self.printer_settings_controller._apply_paper_size(printer, paper_label)
        cols = self.printer_settings_controller._estimate_cols(printer, css_font_pt=10.0, family="DejaVu Sans Mono", paper_label=paper_label)

        w_mm = self.printer_settings_controller.paper_service.get_paper_mm(paper_label)
        w_pt = self.printer_settings_controller._mm_to_points(w_mm)

        # Get render function dari raw painter
        render_func = self.printer_settings_controller._create_doc_raw_painter(
            items, payment, wifi_code, qr_data, cols, setting, transaksi_data_dict, w_pt, printer
        )

        painter = QPainter()
        if not painter.begin(printer):
            self.log_error("Tidak bisa memulai printer.")
            return False

        # Execute render function
        render_func(painter)
        painter.end()
        self.log_info("Struk berhasil dicetak dengan layout full width.")
        return True

    def print_struk_by_mode(
        self,
        transaksi_data,
        detail_data,
        transaksi_data_dict,
        index_printer: Optional[int] = None,
        parent=None
    ):
        try:
            mode = self.print_profile_service.resolve_mode(self.printer_settings_controller)
        except (TypeError, ValueError, KeyError, AttributeError, RuntimeError, OSError, LookupError, ArithmeticError, ImportError) as e:
            self.log_warning(f"Gagal membaca print_mode, fallback preview: {e}")
            mode = "preview"

        if mode == "auto":
            self.log_info("Auto print mode: langsung cetak ke printer")
            return self.print_struk(
                transaksi_data,
                detail_data,
                transaksi_data_dict,
                index_printer=index_printer
            )

        self.log_info("Preview mode: tampilkan preview dulu")
        return self.preview_struk(
            transaksi_data,
            detail_data,
            transaksi_data_dict,
            index_printer=index_printer,
            parent=parent
        )

    def preview_struk(self, transaksi_data, detail_data, transaksi_data_dict, index_printer: Optional[int] = None, parent=None):
        self.log_debug("preview_struk dipanggil dari PrintController")
        if not detail_data or not transaksi_data_dict:
            self.log_warning("Data transaksi tidak lengkap untuk preview")
            return False
        cfg = self._resolve_printer_config(index_printer)
        if not cfg:
            self.log_warning("Tidak ada printer terpilih.")
            return False
        paper_label = cfg.get("paper_size", DEFAULT_PAPER_SIZE)
        w_mm = self.printer_settings_controller.paper_service.get_paper_mm(paper_label)
        preview_printer = self.printer_settings_controller._make_preview_printer(w_mm)

        items, payment, wifi_code, qr_data, setting = self._prepare_print_data(transaksi_data, detail_data, transaksi_data_dict)

        cols = self.printer_settings_controller._estimate_cols(preview_printer, css_font_pt=10.0, family="DejaVu Sans Mono", paper_label=paper_label)
        w_pt = self.printer_settings_controller._mm_to_points(w_mm)

        # Get render function dari raw painter
        render_func = self.printer_settings_controller._create_doc_raw_painter(
            items, payment, wifi_code, qr_data, cols, setting, transaksi_data_dict, w_pt, preview_printer
        )

        # Show preview dengan raw painter
        self.print_view.show_preview_raw(render_func, preview_printer, w_pt)
        self.log_info("Preview struk berhasil ditampilkan.")
        return True

    # edited by glg
    def _resolve_item_discount_nominal(self, detail_item):
        """
        Normalisasi diskon item menjadi nominal line discount (Rp).
        Prioritas:
        1) produk_ord_diskon_nominal / produk_ord_diskon_khusus (nominal eksplisit)
        2) produk_ord_diskon_persen (persen eksplisit)
        3) produk_ord_diskon (legacy: persen <=100, selain itu dianggap nominal)
        """
        def _to_float(value, default=0.0):
            try:
                return float(value)
            except (TypeError, ValueError, KeyError, AttributeError, RuntimeError, OSError, LookupError, ArithmeticError, ImportError):
                return float(default)

        def _to_int(value, default=0):
            try:
                return int(float(value))
            except (TypeError, ValueError, KeyError, AttributeError, RuntimeError, OSError, LookupError, ArithmeticError, ImportError):
                return int(default)

        qty = max(0, _to_int(getattr(detail_item, "produk_ord_jml", 0), 0))
        price = max(0.0, _to_float(getattr(detail_item, "produk_ord_hrg", 0), 0.0))
        gross_line = max(0.0, price * qty)

        nominal_explicit = _to_float(
            getattr(
                detail_item,
                "produk_ord_diskon_nominal",
                getattr(detail_item, "produk_ord_diskon_khusus", 0.0),
            ),
            0.0,
        )
        if nominal_explicit > 0:
            return int(round(min(nominal_explicit, gross_line)))

        persen_explicit = _to_float(
            getattr(detail_item, "produk_ord_diskon_persen", 0.0),
            0.0,
        )
        if persen_explicit <= 0:
            persen_explicit = _to_float(getattr(detail_item, "produk_ord_diskon", 0.0), 0.0)

        if persen_explicit <= 0:
            return 0

        if persen_explicit <= 100:
            nominal_from_percent = gross_line * (persen_explicit / 100.0)
            return int(round(min(nominal_from_percent, gross_line)))

        # Legacy fallback: bila nilai > 100, anggap sebagai nominal lama.
        return int(round(min(persen_explicit, gross_line)))

    def _prepare_print_data(self, transaksi_data, detail_data, transaksi_data_dict):
        items = []
        for d in detail_data:
            try:
                qty = int(d.produk_ord_jml or 0)
                price = int(float(d.produk_ord_hrg or 0))
                disc = self._resolve_item_discount_nominal(d)
                # edited by glg
                produk_jenis = str(getattr(d, "produk_jenis", "") or "").strip().lower()
                promo_free = produk_jenis == "free_produk"
                free = promo_free and price <= 0
            except (TypeError, ValueError, KeyError, AttributeError, RuntimeError, OSError, LookupError, ArithmeticError, ImportError) as e:
                self.log_warning(f"Gagal parsing data item {getattr(d, 'produk_nama', '-')}: {e}")
                qty, price, disc, free, promo_free = 0, 0, 0, False, False
            items.append({
                "name": getattr(d, "produk_nama", "-"),
                "qty": qty,
                "price": price,
                "disc": 0 if free else disc,
                "free": free,
                "promo_free": promo_free,
            })

        # Payment info dari transaksi_data_dict
        payment = {
            "method": "Tunai",  # Default, bisa diganti dengan data payment real
            "jumlah_dibayar": int(transaksi_data_dict.get("transaksi_dibayar", 0)),
            "kembalian": int(transaksi_data_dict.get("transaksi_dibayar_return", 0))
        }

        wifi_code = str(transaksi_data_dict.get("wifi_code") or "-")
        qr_data = self.printer_settings_controller.build_receipt_qr_data(
            transaksi_data_dict=transaksi_data_dict,
            wifi_code=wifi_code
        )

        setting = self.printer_settings_controller.get_receipt_setting()

        return items, payment, wifi_code, qr_data, setting

    def print_settlement(
        self,
        counter,
        kasir_nama,
        shift,
        total_dict,
        transaksi_list,
        admin_nama=None,
        total_disetor=0.0,
        printer_name: Optional[str] = None,
    ):
        # edited by glg
        try:
            return self.settlement_print_service.print_settlement_by_mode(
                printer_settings_controller=self.printer_settings_controller,
                print_view=self.print_view,
                counter=counter,
                kasir_nama=kasir_nama,
                shift=shift,
                total_dict=total_dict,
                transaksi_list=transaksi_list,
                admin_nama=admin_nama,
                total_disetor=total_disetor,
                printer_name=printer_name,
            )
        except (TypeError, ValueError, KeyError, AttributeError, RuntimeError, OSError, LookupError, ArithmeticError, ImportError) as exc:
            self.log_error(f"[SettlementPrint] Exception: {exc}")
            return False
