import datetime
import uuid

from pypos.core.utils.myhelper import parse_rupiah
from pypos.core.utils.device_utils import get_active_device_info, get_device_id
from pypos.modules.penjualan.errors import SettlementProcessError


class SettlementOrchestratorService:
    def __init__(self, model, print_controller):
        self.model = model
        self.print_controller = print_controller

    def validate_admin(self, admin_name, password):
        admin_name = str(admin_name or "").strip()
        if not admin_name:
            return False, "Username admin belum diisi."
        admin_status = self.model.get_admin_settlement_status(admin_name)
        if not admin_status:
            return False, "Username admin tidak ditemukan."
        if int(admin_status.get("oto_settlement", 0) or 0) != 1:
            return False, "Hanya admin dengan hak akses settlement yang dapat melakukan settlement."
        if not self.model.verifikasi_admin(admin_name, password):
            return False, "Password admin salah."
        return True, ""

    def parse_total_disetor(self, uang_text, total_non_tunai):
        text = str(uang_text or "").strip()
        if not text:
            if float(total_non_tunai or 0) <= 0:
                return False, 0.0, "Jumlah uang setor belum diisi."
            return True, 0.0, ""
        cleaned = text.replace(" ", "")
        if "-" in cleaned:
            return False, 0.0, "Nominal setoran tidak valid."
        try:
            total_disetor = float(parse_rupiah(cleaned))
        except (TypeError, ValueError):
            return False, 0.0, "Format uang tidak valid."
        if total_disetor < 0:
            return False, 0.0, "Nominal setoran tidak valid."
        return True, total_disetor, ""

    def _resolve_shift(self, user_info):
        shift = user_info.get("shift") if isinstance(user_info, dict) else None
        if shift is None:
            return "-"
        return str(shift)

    def _resolve_context(self, user_info):
        user_info = user_info or {}
        kasir_id = user_info.get("id")
        kasir_nama = str(user_info.get("nama") or "-")
        try:
            cabang_id = int(user_info.get("cabang_id") or 0)
        except (TypeError, ValueError):
            cabang_id = 0
        cabang_nama = str(
            user_info.get("cabang_nama")
            or user_info.get("nama_cabang")
            or user_info.get("branch_name")
            or "-"
        )
        # edited by glg
        # Fallback context device agar cabang settlement tidak kosong saat user_info minim.
        if cabang_id <= 0:
            try:
                machine_id = str(user_info.get("machine_id") or get_device_id() or "").strip()
                active_device = get_active_device_info(machine_id) or {}
                cabang_id = int(active_device.get("cabang_id") or 0)
                if cabang_nama in {"", "-"}:
                    cabang_nama = str(active_device.get("cabang_nama") or cabang_nama)
            except (TypeError, ValueError):
                cabang_id = 0
        shift = self._resolve_shift(user_info)
        return {
            "kasir_id": kasir_id,
            "kasir_nama": kasir_nama,
            "cabang_id": cabang_id,
            "cabang_nama": cabang_nama,
            "shift": shift,
        }

    # edited by glg
    @staticmethod
    def _generate_trace_id(scope: str = "settlement") -> str:
        prefix = str(scope or "settlement").strip().lower().replace(" ", "_")
        stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[-12:]
        suffix = uuid.uuid4().hex[:8]
        return f"{prefix}-{stamp}-{suffix}"

    def execute_settlement(self, transaksi_data, total_disetor, admin_name, user_info):
        transaksi_data = transaksi_data or []
        trace_id = self._generate_trace_id("settlement")
        if not transaksi_data:
            return {
                "ok": False,
                "message": "Tidak ada transaksi yang perlu disettle.",
                "error_code": "SETTLEMENT_EMPTY_TRANSAKSI_IDS",
                "reason": "empty_transaksi_ids",
                "trace_id": trace_id,
            }

        # edited by glg
        # Pastikan approval settlement tersimpan dari data admin real (bukan fallback export).
        admin_status = self.model.get_admin_settlement_status(admin_name) or {}
        try:
            approval_id = int(admin_status.get("id") or 0)
        except (TypeError, ValueError):
            approval_id = 0
        approval_nama = str(admin_status.get("nama_login") or admin_name or "").strip()

        context = self._resolve_context(user_info)
        tanggal_list = list({t.get("tanggal") for t in transaksi_data if t.get("tanggal")})
        kasir_list = list({t.get("kasir") for t in transaksi_data if t.get("kasir")})
        transaksi_ids = self.model.get_transaksi_ids_belum_settle(
            tanggal_list=tanggal_list,
            kasir_list=kasir_list,
            include_legacy_non_tunai=True,
        )
        if not transaksi_ids:
            return {
                "ok": False,
                "message": "Tidak ada ID transaksi untuk di-settle.",
                "error_code": "SETTLEMENT_EMPTY_TRANSAKSI_IDS",
                "reason": "empty_transaksi_ids",
                "trace_id": trace_id,
            }

        # edited by glg
        # Jalur utama: simpan settlement + lock transaksi berjalan atomik dalam satu transaksi DB.
        counter = None
        execute_atomic = getattr(self.model, "execute_settlement_atomic", None)
        if callable(execute_atomic):
            atomic_result = execute_atomic(
                kasir_id=context["kasir_id"],
                kasir_nama=context["kasir_nama"],
                cabang_id=context["cabang_id"],
                cabang_nama=context["cabang_nama"],
                transaksi_list=transaksi_data,
                approval_id=approval_id,
                approval_nama=approval_nama,
                transaksi_ids_override=transaksi_ids,
                admin=admin_name,
                total_disetor=total_disetor,
                trace_id=trace_id,
            )
            if not bool((atomic_result or {}).get("ok")):
                return {
                    "ok": False,
                    "message": str((atomic_result or {}).get("message") or "Gagal menyimpan settlement."),
                    "error_code": str((atomic_result or {}).get("error_code") or "SETTLEMENT_ATOMIC_ERROR"),
                    "reason": str((atomic_result or {}).get("reason") or "settlement_process_error"),
                    "trace_id": str((atomic_result or {}).get("trace_id") or trace_id),
                }
            counter = (atomic_result or {}).get("counter")
            transaksi_ids = list((atomic_result or {}).get("transaksi_ids") or transaksi_ids)
        else:
            # edited by glg
            # Fallback kompatibilitas model lama yang belum punya executor atomik.
            simpan_kwargs = {
                "kasir_id": context["kasir_id"],
                "kasir_nama": context["kasir_nama"],
                "cabang_id": context["cabang_id"],
                "cabang_nama": context["cabang_nama"],
                "transaksi_list": transaksi_data,
                "approval_id": approval_id,
                "approval_nama": approval_nama,
                "transaksi_ids_override": transaksi_ids,
                "trace_id": trace_id,
            }
            try:
                try:
                    counter = self.model.simpan_settlement(**simpan_kwargs)
                except TypeError:
                    simpan_kwargs.pop("transaksi_ids_override", None)
                    counter = self.model.simpan_settlement(**simpan_kwargs)
            except SettlementProcessError as exc:
                return {
                    "ok": False,
                    "message": str(exc),
                    "error_code": str(getattr(exc, "code", "SETTLEMENT_INSERT_ERROR") or "SETTLEMENT_INSERT_ERROR"),
                    "reason": "settlement_process_error",
                    "trace_id": trace_id,
                }
            if not counter:
                return {
                    "ok": False,
                    "message": "Gagal menyimpan settlement.",
                    "error_code": "SETTLEMENT_COUNTER_EMPTY",
                    "reason": "counter_empty",
                    "trace_id": trace_id,
                }

            try:
                settled_ok = self.model.set_settlement(
                    transaksi_ids,
                    admin_name,
                    context["kasir_nama"],
                    total_disetor,
                    trace_id=trace_id,
                )
            except SettlementProcessError as exc:
                return {
                    "ok": False,
                    "message": str(exc),
                    "error_code": str(getattr(exc, "code", "SETTLEMENT_DB_ERROR") or "SETTLEMENT_DB_ERROR"),
                    "reason": "settlement_process_error",
                    "trace_id": trace_id,
                }
            if settled_ok is False:
                return {
                    "ok": False,
                    "message": "Gagal memperbarui status settlement transaksi.",
                    "error_code": "SETTLEMENT_STATUS_UPDATE_FAILED",
                    "reason": "status_update_failed",
                    "trace_id": trace_id,
                }

        # edited by glg
        # Print settlement harus menggunakan scope transaksi_ids batch yang sama.
        total_dict = self.model.hitung_total_per_metode_by_ids(transaksi_ids)
        detail_transaksi_list = self.model.get_transaksi_by_ids(transaksi_ids)
        if not detail_transaksi_list:
            detail_transaksi_list = transaksi_data
        print_ok = True
        print_error = ""
        try:
            # edited by glg
            # Backward-compatibility:
            # print_controller lama menerima signature positional tanpa admin_nama/total_disetor.
            try:
                result = self.print_controller.print_settlement(
                    counter=counter,
                    kasir_nama=context["kasir_nama"],
                    shift=context["shift"],
                    total_dict=total_dict,
                    transaksi_list=detail_transaksi_list,
                    admin_nama=admin_name,
                    total_disetor=total_disetor,
                )
            except TypeError:
                result = self.print_controller.print_settlement(
                    counter,
                    context["kasir_nama"],
                    context["shift"],
                    total_dict,
                    detail_transaksi_list,
                )
            if result is False:
                print_ok = False
                print_error = "Cetak settlement tidak berhasil."
        except (RuntimeError, TypeError, AttributeError) as exc:
            print_ok = False
            print_error = str(exc)

        return {
            "ok": True,
            "counter": counter,
            "transaksi_ids": transaksi_ids,
            "print_ok": print_ok,
            "print_error": print_error,
            "context": context,
            "trace_id": trace_id,
        }
