from pypos.core.base_controller import BaseController
from pypos.core.utils.path_utils import get_db_path
from pypos.modules.penjualan.errors import PembatalanProcessError
from pypos.modules.penjualan.models.pembatalan_transaksi_model import PembatalanTransaksiModel
from pypos.modules.penjualan.models.settlement_model import SettlementModel
from pypos.modules.penjualan.services.admin_authorization_service import AdminAuthorizationService
from pypos.modules.penjualan.services.pembatalan_policy_service import PembatalanPolicyService
from pypos.modules.penjualan.services.pembayaran_admin_dialog_service import PembayaranAdminDialogService
from pypos.modules.penjualan.services.penjualan_config_service import PenjualanConfigService
from pypos.modules.penjualan.services.settlement_mutation_lock_service import (
    SettlementMutationLockService,
)


class PembatalanTransaksiController(BaseController):
    def __init__(
        self,
        model: PembatalanTransaksiModel = None,
        user_info=None,
        settlement_model: SettlementModel = None,
        db_path: str = None,
    ):
        super().__init__()
        resolved_db_path = db_path or getattr(model, "db_path", None) or get_db_path()
        self.model = model or PembatalanTransaksiModel(db_path=resolved_db_path)
        self.user_info = user_info or {}
        self.penjualan_config_service = PenjualanConfigService(db_path=resolved_db_path)
        self.model_settle = settlement_model or SettlementModel(resolved_db_path)
        self.policy_service = PembatalanPolicyService()
        self.admin_authorization_service = AdminAuthorizationService(self.model_settle)
        self.admin_dialog_service = PembayaranAdminDialogService(
            validator=self._validate_admin_pembatalan
        )
        # edited by glg
        # Shared lock validator agar rule settlement return/pembatalan konsisten.
        self.settlement_lock_service = SettlementMutationLockService(
            db_path=resolved_db_path,
            log_warning=self.log_warning,
        )

    def _get_allowed_date_range(self):
        allowed_days = self.penjualan_config_service.get_pembatalan_allowed_days(default=0)
        return self.policy_service.get_allowed_date_range(allowed_days)

    def get_allowed_date_range(self):
        return self._get_allowed_date_range()

    # edited by glg
    def _to_row_dict(self, row):
        if isinstance(row, dict):
            return dict(row)
        try:
            return dict(row)
        except (TypeError, ValueError):
            return {}

    # edited by glg
    def _get_settlement_lock_map(self, transaksi_ids):
        normalized_ids = []
        for raw_id in list(transaksi_ids or []):
            normalized = self._normalize_transaksi_id(raw_id)
            if normalized:
                normalized_ids.append(int(normalized))
        try:
            return self.settlement_lock_service.get_lock_map(normalized_ids)
        except (RuntimeError, TypeError, ValueError) as exc:
            self.log_warning(f"Gagal memuat lock settlement pembatalan: {exc}")
            # edited by glg
            # Fail-safe: jika lock map gagal, blokir pembatalan untuk seluruh transaksi target.
            return {int(trx_id): True for trx_id in normalized_ids}

    # edited by glg
    def _filter_rows_belum_settle(self, rows):
        row_dicts = [self._to_row_dict(row) for row in (rows or [])]
        transaksi_ids = []
        for row in row_dicts:
            normalized_id = self._normalize_transaksi_id(row.get("id"))
            if normalized_id:
                transaksi_ids.append(int(normalized_id))
        lock_map = self._get_settlement_lock_map(transaksi_ids)
        filtered = []
        for row in row_dicts:
            normalized_id = self._normalize_transaksi_id(row.get("id"))
            if normalized_id and lock_map.get(int(normalized_id), False):
                continue
            filtered.append(row)
        return filtered

    # edited by glg
    def _is_transaksi_locked_by_settlement(self, transaksi_id: str):
        normalized_id = self._normalize_transaksi_id(transaksi_id)
        if not normalized_id:
            return False
        lock_map = self._get_settlement_lock_map([int(normalized_id)])
        return bool(lock_map.get(int(normalized_id), False))

    def load_transaksi(self, keyword: str = ""):
        limit = self.penjualan_config_service.get_pembatalan_search_limit(default=200)
        start_date, end_date = self._get_allowed_date_range()
        rows = self.safe_call(
            self.model.search_transaksi,
            keyword=keyword,
            start_date=start_date.strftime("%Y-%m-%d"),
            end_date=end_date.strftime("%Y-%m-%d"),
            limit=limit,
            default=[],
            on_error=lambda exc: self.log_warning(f"Gagal memuat transaksi pembatalan: {exc}"),
        )
        return self._filter_rows_belum_settle(rows)

    # edited by glg
    def _to_non_negative_int(self, value, default=0):
        try:
            parsed = int(float(value))
        except (TypeError, ValueError):
            parsed = int(default or 0)
        return parsed if parsed >= 0 else int(default or 0)

    # edited by glg
    def _normalize_transaksi_id(self, transaksi_id):
        raw = str(transaksi_id or "").strip()
        if not raw:
            return ""
        try:
            parsed = int(raw)
        except (TypeError, ValueError):
            return ""
        return str(parsed) if parsed > 0 else ""

    # edited by glg
    def _get_cancel_context(self, transaksi_id: str):
        normalized_id = self._normalize_transaksi_id(transaksi_id)
        if not normalized_id:
            return None
        return self.safe_call(
            self.model.get_transaksi_cancel_context,
            normalized_id,
            default=None,
            on_error=lambda exc: self.log_warning(f"Gagal memuat context pembatalan transaksi {normalized_id}: {exc}"),
        )

    # edited by glg
    def _validate_cancel_context(self, cancel_context):
        context = cancel_context if isinstance(cancel_context, dict) else {}
        if not context:
            return False, "Transaksi tidak ditemukan."
        if self._to_non_negative_int(context.get("trash"), 0) == 1:
            return False, "Transaksi sudah dibatalkan."
        if self._to_non_negative_int(context.get("settlement_id"), 1) == 0:
            return False, "Transaksi sudah disettle dan tidak dapat dibatalkan."
        transaksi_id = self._normalize_transaksi_id(context.get("id"))
        if transaksi_id and self._is_transaksi_locked_by_settlement(transaksi_id):
            return False, "Transaksi sudah disettle dan tidak dapat dibatalkan."
        jenis_label = str(context.get("jenis_label") or "").strip().lower()
        if jenis_label != "invoice":
            return False, "Hanya transaksi invoice yang dapat dibatalkan."
        return True, ""

    def can_batalkan(self, transaksi_id: str) -> bool:
        normalized_id = self._normalize_transaksi_id(transaksi_id)
        if not normalized_id:
            return False
        context = self._get_cancel_context(normalized_id)
        is_valid, _message = self._validate_cancel_context(context)
        if not is_valid:
            return False
        raw_date = context.get("dtime")
        if not raw_date:
            return False
        start_date, end_date = self._get_allowed_date_range()
        return self.policy_service.is_transaksi_date_allowed(raw_date, start_date, end_date)

    def batalkan_transaksi(self, transaksi_id: str, admin_name: str = ""):
        normalized_id = self._normalize_transaksi_id(transaksi_id)
        if not normalized_id:
            return False, "ID transaksi tidak valid."
        context = self._get_cancel_context(normalized_id)
        is_valid, validation_message = self._validate_cancel_context(context)
        if not is_valid:
            return False, validation_message
        start_date, end_date = self._get_allowed_date_range()
        if not self.policy_service.is_transaksi_date_allowed(context.get("dtime"), start_date, end_date):
            return False, "Transaksi di luar periode pembatalan yang diizinkan."
        try:
            self.model.delete_transaksi(
                normalized_id,
                admin_name=admin_name,
                dibatalkan_oleh_id=self.user_info.get("id"),
                dibatalkan_oleh_nama=self.user_info.get("nama"),
            )
        except PembatalanProcessError as exc:
            error_code = str(getattr(exc, "code", "CANCEL_PROCESS_ERROR") or "CANCEL_PROCESS_ERROR")
            error_message = str(getattr(exc, "message", "") or "").strip()
            self.log_error(f"Gagal membatalkan transaksi {normalized_id} [{error_code}]: {exc}")
            if error_message:
                return False, f"{error_message} (Kode: {error_code})"
            return False, f"Gagal membatalkan transaksi. (Kode: {error_code})"
        except (TypeError, ValueError) as exc:
            error_code = "CANCEL_CONTROLLER_DATA_ERROR"
            self.log_error(f"Gagal membatalkan transaksi {normalized_id} [{error_code}]: {exc}")
            return False, f"Data pembatalan transaksi tidak valid. (Kode: {error_code})"
        except RuntimeError as exc:
            error_code = "CANCEL_CONTROLLER_RUNTIME_ERROR"
            self.log_error(f"Gagal membatalkan transaksi {normalized_id} [{error_code}]: {exc}")
            return False, f"Gagal membatalkan transaksi. (Kode: {error_code})"
        except Exception as exc:
            error_code = "CANCEL_CONTROLLER_UNEXPECTED_ERROR"
            self.log_error(f"Gagal membatalkan transaksi {normalized_id} [{error_code}]: {exc}")
            return False, f"Gagal membatalkan transaksi. (Kode: {error_code})"
        return True, "Transaksi berhasil dibatalkan."

    def load_history(self, keyword: str = "", start_date: str = None, end_date: str = None):
        limit = self.penjualan_config_service.get_pembatalan_search_limit(default=200)
        return self.safe_call(
            self.model.get_history,
            keyword=keyword,
            start_date=start_date,
            end_date=end_date,
            limit=limit,
            default=[],
            on_error=lambda exc: self.log_warning(f"Gagal memuat history pembatalan: {exc}"),
        )

    def load_history_items(self, transaksi_id: str):
        return self.safe_call(
            self.model.get_history_items,
            transaksi_id,
            default=[],
            on_error=lambda exc: self.log_warning(f"Gagal memuat item history pembatalan {transaksi_id}: {exc}"),
        )

    def _validate_admin_pembatalan(self, admin_name, password):
        return self.safe_call(
            self.admin_authorization_service.validate_settlement_admin,
            admin_name,
            password,
            default=(False, "Gagal melakukan verifikasi admin."),
            on_error=lambda exc: self.log_warning(f"Gagal validasi admin pembatalan: {exc}"),
        )

    def verifikasi_admin_pembatalan(self, parent=None):
        return self.admin_dialog_service.verify(
            parent=parent,
            warn_callback=lambda title, message, dialog: self.show_warning(title, message, view=dialog),
            return_admin_name=True,
        )
