# edited by glg
import sqlite3

import pytest

from pypos.modules.penjualan.errors import TransaksiSaveError
from pypos.modules.penjualan.services.transaksi_persist_flow_service import (
    TransaksiPersistFlowService,
)

pytestmark = [pytest.mark.unit, pytest.mark.critical_flow]


class _SaveServiceSuccess:
    def save_with_audit(self, **_kwargs):
        return 88, [
            {"free_produk_nama": "Bonus A", "free_qty": 2},
            {"free_produk_nama": "Bonus A", "free_qty": 1},
            {"free_produk_nama": "Bonus B", "free_qty": "3"},
        ]


class _SaveServiceDataError:
    def save_with_audit(self, **_kwargs):
        raise ValueError("payload_invalid")


class _SaveServiceTrxError:
    def save_with_audit(self, **_kwargs):
        raise TransaksiSaveError("TRX_SAVE_DB_ERROR", "Gagal menyimpan transaksi ke database.")


class _SaveServiceSQLiteError:
    def save_with_audit(self, **_kwargs):
        raise sqlite3.OperationalError("db_locked")


class _SaveServiceRuntimeError:
    def save_with_audit(self, **_kwargs):
        raise RuntimeError("runtime_boom")


class _SaveServiceShouldNotBeCalled:
    def save_with_audit(self, **_kwargs):
        raise AssertionError("save_with_audit tidak boleh dipanggil pada duplicate success")


class _SaveServiceCaptureTrace:
    def __init__(self):
        self.kwargs = {}

    def save_with_audit(self, **kwargs):
        self.kwargs = dict(kwargs or {})
        return 109, []


class _EnterpriseControlStub:
    def __init__(self, lock_state=None):
        self.lock_state = lock_state if isinstance(lock_state, dict) else {"proceed": True, "state": "STARTED"}
        self.success_marks = []
        self.failed_marks = []
        self.audit_rows = []

    def acquire_idempotency_lock(self, **_kwargs):
        return dict(self.lock_state)

    def mark_idempotency_success(self, **kwargs):
        self.success_marks.append(dict(kwargs))

    def mark_idempotency_failed(self, **kwargs):
        self.failed_marks.append(dict(kwargs))

    def append_persist_audit(self, **kwargs):
        self.audit_rows.append(dict(kwargs))
        return len(self.audit_rows)

    @staticmethod
    def build_payload_hash(**_kwargs):
        return "payload-hash-01"


def test_transaksi_persist_flow_service_success_builds_free_item_summary():
    svc = TransaksiPersistFlowService(_SaveServiceSuccess())

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={},
        audit_message="unit_test",
    )

    assert bool(result.get("ok")) is True
    assert int(result.get("transaksi_id") or 0) == 88
    assert result.get("free_items_summary") == [
        {"nama": "Bonus A", "qty": 3},
        {"nama": "Bonus B", "qty": 3},
    ]


def test_transaksi_persist_flow_service_maps_data_error_to_standard_code():
    svc = TransaksiPersistFlowService(_SaveServiceDataError())

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={},
        audit_message="unit_test",
    )

    assert bool(result.get("ok")) is False
    err = result.get("error") or {}
    assert err.get("error_code") == "TRX_SAVE_CONTROLLER_DATA_ERROR"
    assert err.get("reason") == "invalid_transaksi_data"
    assert str(err.get("trace_id") or "").startswith("trx_save-")


def test_transaksi_persist_flow_service_keeps_transaksi_save_error_code():
    svc = TransaksiPersistFlowService(_SaveServiceTrxError())

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={},
        audit_message="unit_test",
    )

    assert bool(result.get("ok")) is False
    err = result.get("error") or {}
    assert err.get("error_code") == "TRX_SAVE_DB_ERROR"
    assert err.get("reason") == "db_error"


@pytest.mark.retry
@pytest.mark.enterprise
def test_transaksi_persist_flow_service_duplicate_success_return_idempotent_replay():
    enterprise = _EnterpriseControlStub(
        lock_state={
            "proceed": False,
            "state": "DUPLICATE_SUCCESS",
            "transaksi_id": 911,
        }
    )
    svc = TransaksiPersistFlowService(
        _SaveServiceShouldNotBeCalled(),
        enterprise_control_service=enterprise,
    )

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={},
        audit_message="unit_test",
    )

    assert bool(result.get("ok")) is True
    assert bool(result.get("idempotent_replay")) is True
    assert int(result.get("transaksi_id") or 0) == 911
    assert not enterprise.success_marks


@pytest.mark.retry
@pytest.mark.enterprise
def test_transaksi_persist_flow_service_conflict_maps_to_error_envelope():
    enterprise = _EnterpriseControlStub(
        lock_state={
            "proceed": False,
            "state": "CONFLICT",
            "reason": "idempotency_key_conflict",
            "error_code": "TRX_IDEMPOTENCY_CONFLICT",
        }
    )
    svc = TransaksiPersistFlowService(
        _SaveServiceSuccess(),
        enterprise_control_service=enterprise,
    )

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={"idempotency_key": "same-key"},
        audit_message="unit_test_conflict",
    )

    assert bool(result.get("ok")) is False
    err = result.get("error") or {}
    assert err.get("error_code") == "TRX_IDEMPOTENCY_CONFLICT"
    assert err.get("reason") == "idempotency_key_conflict"
    assert str(err.get("trace_id") or "").startswith("trx_save-")
    assert enterprise.failed_marks


@pytest.mark.retry
@pytest.mark.enterprise
def test_transaksi_persist_flow_service_idempotency_in_progress_rejected():
    enterprise = _EnterpriseControlStub(
        lock_state={
            "proceed": False,
            "state": "IN_PROGRESS",
            "reason": "idempotency_in_progress",
            "error_code": "TRX_IDEMPOTENCY_IN_PROGRESS",
        }
    )
    svc = TransaksiPersistFlowService(
        _SaveServiceSuccess(),
        enterprise_control_service=enterprise,
    )

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={"idempotency_key": "same-key"},
        audit_message="unit_test_in_progress",
    )

    assert bool(result.get("ok")) is False
    err = result.get("error") or {}
    assert err.get("error_code") == "TRX_IDEMPOTENCY_IN_PROGRESS"
    assert err.get("reason") == "idempotency_in_progress"


@pytest.mark.chaos
def test_transaksi_persist_flow_service_maps_sqlite_error_to_standard_code():
    svc = TransaksiPersistFlowService(_SaveServiceSQLiteError())

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={},
        audit_message="unit_test_sqlite",
    )

    assert bool(result.get("ok")) is False
    err = result.get("error") or {}
    assert err.get("error_code") == "TRX_SAVE_SQLITE_ERROR"
    assert err.get("reason") == "sqlite_error"


@pytest.mark.chaos
def test_transaksi_persist_flow_service_maps_runtime_error_to_standard_code():
    svc = TransaksiPersistFlowService(_SaveServiceRuntimeError())

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={},
        audit_message="unit_test_runtime",
    )

    assert bool(result.get("ok")) is False
    err = result.get("error") or {}
    assert err.get("error_code") == "TRX_SAVE_RUNTIME_ERROR"
    assert err.get("reason") == "runtime_error"


def test_transaksi_persist_flow_service_generates_idempotency_key_if_missing():
    enterprise = _EnterpriseControlStub()
    svc = TransaksiPersistFlowService(
        _SaveServiceSuccess(),
        enterprise_control_service=enterprise,
    )

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict={"nomer": "INV-77"},
        audit_message="unit_test_key",
    )

    assert bool(result.get("ok")) is True
    assert str(result.get("idempotency_key") or "").startswith("trx-inv-77")


def test_transaksi_persist_flow_service_meneruskan_trace_id_ke_save_service():
    save_service = _SaveServiceCaptureTrace()
    svc = TransaksiPersistFlowService(save_service)
    transaksi_data_dict = {"nomer": "INV-TRACE-01"}

    result = svc.persist(
        transaksi_data=[],
        detail_data=[],
        transaksi_data_dict=transaksi_data_dict,
        audit_message="unit_test_trace",
    )

    assert bool(result.get("ok")) is True
    trace_id = str(result.get("trace_id") or "")
    assert trace_id.startswith("trx_save-")
    assert str(transaksi_data_dict.get("trace_id") or "") == trace_id
    assert str(save_service.kwargs.get("trace_id") or "") == trace_id
