# edited by glg
import sqlite3
import time

from pypos.core.utils.db_helper import connect_sqlite
from pypos.modules.penjualan.errors import TransaksiSaveError


class TransaksiCounterService:
    # edited by glg
    _LOCK_RETRY_DELAYS_SEC = (0.03, 0.06, 0.12)

    def __init__(self, db_path: str, log_warning=None):
        self.db_path = db_path
        self.log_warning = log_warning if callable(log_warning) else (lambda *_args, **_kwargs: None)

    # edited by glg
    @staticmethod
    def _is_lock_contention_error(exc):
        payload = str(exc or "").lower()
        return any(
            token in payload
            for token in (
                "database is locked",
                "database table is locked",
                "database schema is locked",
                "database is busy",
            )
        )

    # edited by glg
    def _increment_counter_atomic(self, cursor, normalized_name):
        # Kunci write transaction agar increment counter tetap atomik antar proses/thread.
        cursor.execute("BEGIN IMMEDIATE")
        cursor.execute(
            """
            CREATE TABLE IF NOT EXISTS penomoran (
                nama TEXT PRIMARY KEY,
                counter INTEGER DEFAULT 0,
                dtime_update TEXT
            )
            """
        )
        cursor.execute(
            """
            INSERT OR IGNORE INTO penomoran (nama, counter, dtime_update)
            VALUES (?, 0, datetime('now'))
            """,
            (normalized_name,),
        )
        cursor.execute(
            """
            UPDATE penomoran
            SET counter = COALESCE(counter, 0) + 1,
                dtime_update = datetime('now')
            WHERE nama = ?
            """,
            (normalized_name,),
        )
        if int(cursor.rowcount or 0) != 1:
            raise TransaksiSaveError(
                "TRX_COUNTER_UPDATE_CONFLICT",
                "Counter transaksi gagal diperbarui secara atomik.",
            )
        cursor.execute("SELECT counter FROM penomoran WHERE nama = ?", (normalized_name,))
        row = cursor.fetchone()
        return int((row[0] if row else 0) or 0)

    def get_and_increment_counter(self, nama: str = "transaksi") -> int:
        normalized_name = str(nama or "transaksi").strip() or "transaksi"
        conn = connect_sqlite(self.db_path)
        cursor = conn.cursor()
        try:
            for attempt_idx in range(len(self._LOCK_RETRY_DELAYS_SEC) + 1):
                try:
                    new_counter = self._increment_counter_atomic(cursor, normalized_name)
                    conn.commit()
                    return int(new_counter)
                except sqlite3.OperationalError as exc:
                    try:
                        conn.rollback()
                    except sqlite3.Error:
                        pass
                    if (
                        attempt_idx >= len(self._LOCK_RETRY_DELAYS_SEC)
                        or not self._is_lock_contention_error(exc)
                    ):
                        raise
                    wait_sec = float(self._LOCK_RETRY_DELAYS_SEC[attempt_idx])
                    self.log_warning(
                        f"Counter transaksi lock contention, retry={attempt_idx + 1}, wait_ms={int(wait_sec * 1000)}"
                    )
                    time.sleep(wait_sec)
        except sqlite3.Error as exc:
            try:
                conn.rollback()
            except sqlite3.Error:
                pass
            raise TransaksiSaveError(
                "TRX_COUNTER_DB_ERROR",
                "Gagal memperbarui counter transaksi.",
                cause=exc,
            ) from exc
        except (TypeError, ValueError) as exc:
            try:
                conn.rollback()
            except sqlite3.Error:
                pass
            raise TransaksiSaveError(
                "TRX_COUNTER_INVALID_VALUE",
                "Nilai counter transaksi tidak valid.",
                cause=exc,
            ) from exc
        finally:
            try:
                cursor.close()
            except sqlite3.Error as exc:
                self.log_warning(f"Gagal menutup cursor counter transaksi: {exc}")
            try:
                conn.close()
            except sqlite3.Error as exc:
                self.log_warning(f"Gagal menutup koneksi counter transaksi: {exc}")
