# edited by glg
import sqlite3
import tempfile
from pathlib import Path

import pytest

from pypos.modules.penjualan.services.free_produk_sync_outbox_service import (
    FreeProdukSyncOutboxService,
)
from pypos.modules.penjualan.services.transaksi_enterprise_control_service import (
    TransaksiEnterpriseControlService,
)

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


def _query_one(db_path, query, params=None):
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    cur = conn.cursor()
    cur.execute(query, tuple(params or []))
    row = cur.fetchone()
    conn.close()
    return row


def test_enterprise_control_schema_membuat_tabel_dan_trigger():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_schema.db")
        _ = TransaksiEnterpriseControlService(db_path=db_path)

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        cur.execute(
            """
            SELECT name
            FROM sqlite_master
            WHERE type IN ('table', 'trigger')
            """
        )
        names = {str(row[0]) for row in (cur.fetchall() or [])}
        conn.close()

        assert "transaksi_idempotency" in names
        assert "transaksi_persist_audit" in names
        assert "transaksi_approval_trail" in names
        assert "trg_transaksi_persist_audit_immutable_update" in names
        assert "trg_transaksi_approval_trail_immutable_delete" in names


@pytest.mark.retry
def test_acquire_idempotency_lock_pertama_berhasil_started():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_lock_start.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)

        state = service.acquire_idempotency_lock(
            idempotency_key="trx-1",
            payload_hash="hash-1",
            trace_id="trace-1",
        )
        assert bool(state.get("proceed")) is True
        assert state.get("state") == "STARTED"


@pytest.mark.retry
def test_acquire_idempotency_lock_conflict_jika_hash_berbeda():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_lock_conflict.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        _ = service.acquire_idempotency_lock(
            idempotency_key="trx-1",
            payload_hash="hash-a",
            trace_id="trace-a",
        )

        state = service.acquire_idempotency_lock(
            idempotency_key="trx-1",
            payload_hash="hash-b",
            trace_id="trace-b",
        )
        assert bool(state.get("proceed")) is False
        assert state.get("state") == "CONFLICT"
        assert state.get("error_code") == "TRX_IDEMPOTENCY_CONFLICT"


@pytest.mark.retry
def test_acquire_idempotency_lock_duplicate_success_setelah_mark_success():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_lock_success.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        _ = service.acquire_idempotency_lock(
            idempotency_key="trx-ok",
            payload_hash="hash-ok",
            trace_id="trace-ok",
        )
        service.mark_idempotency_success(
            idempotency_key="trx-ok",
            transaksi_id=123,
            trace_id="trace-ok",
        )

        state = service.acquire_idempotency_lock(
            idempotency_key="trx-ok",
            payload_hash="hash-ok",
            trace_id="trace-ok-2",
        )
        assert bool(state.get("proceed")) is False
        assert state.get("state") == "DUPLICATE_SUCCESS"
        assert int(state.get("transaksi_id") or 0) == 123


@pytest.mark.chaos
def test_append_persist_audit_immutable_update_ditolak():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_immutable_audit.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        row_id = service.append_persist_audit(
            event_type="STARTED",
            status="processing",
            idempotency_key="trx-immutable",
            trace_id="trace-audit",
            payload={"x": 1},
        )
        assert int(row_id) > 0

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        try:
            with pytest.raises(sqlite3.Error):
                cur.execute(
                    """
                    UPDATE transaksi_persist_audit
                    SET status = 'tampered'
                    WHERE id = ?
                    """,
                    (row_id,),
                )
            conn.rollback()
        finally:
            cur.close()
            conn.close()


@pytest.mark.chaos
def test_record_approval_trail_immutable_delete_ditolak():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_immutable_approval.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        row_id = service.record_approval_trail(
            action_name="diskon",
            actor_name="kasir-a",
            approval_name="admin-a",
            approval_status="approved",
            trace_id="trace-approval",
            payload={"diskon": 10},
        )
        assert int(row_id) > 0

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        try:
            with pytest.raises(sqlite3.Error):
                cur.execute("DELETE FROM transaksi_approval_trail WHERE id = ?", (row_id,))
            conn.rollback()
        finally:
            cur.close()
            conn.close()


def test_run_reconciliation_menandai_stale_idempotency_jadi_failed():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_reconcile_idempotency.db")
        service = TransaksiEnterpriseControlService(db_path=db_path, stale_inprogress_seconds=60)

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        cur.execute(
            """
            INSERT INTO transaksi_idempotency (
                idempotency_key, payload_hash, status, transaksi_id,
                trace_id, error_code, error_reason, created_at, updated_at
            )
            VALUES (?, ?, 'IN_PROGRESS', 0, ?, NULL, NULL, ?, ?)
            """,
            (
                "trx-stale",
                "hash-stale",
                "trace-stale",
                "2000-01-01 00:00:00",
                "2000-01-01 00:00:00",
            ),
        )
        conn.commit()
        conn.close()

        summary = service.run_reconciliation()
        assert int(summary.get("stale_idempotency_failed") or 0) >= 1

        row = _query_one(
            db_path,
            """
            SELECT status, error_code
            FROM transaksi_idempotency
            WHERE idempotency_key = ?
            """,
            ["trx-stale"],
        )
        assert row is not None
        assert str(row["status"]) == "FAILED"
        assert str(row["error_code"]) == "TRX_IDEMPOTENCY_STALE"


def test_run_reconciliation_recover_stale_outbox_inflight():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_reconcile_outbox.db")
        service = TransaksiEnterpriseControlService(db_path=db_path, stale_inprogress_seconds=60)
        outbox = FreeProdukSyncOutboxService(db_path=db_path)

        queued = outbox.enqueue_payload(
            [{"transaksi_id": 71, "produk_id": 710, "free_produk_id": 711, "free_qty": 1}],
            reason="test_reconcile_outbox",
        )
        outbox_id = int(queued.get("id") or 0)
        assert outbox_id > 0
        _ = outbox.claim_due_payloads(max_items=1)

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        cur.execute(
            """
            UPDATE free_produk_sync_outbox
            SET status = 'INFLIGHT', lease_until = '2000-01-01 00:00:00'
            WHERE id = ?
            """,
            (outbox_id,),
        )
        conn.commit()
        conn.close()

        summary = service.run_reconciliation()
        assert int(summary.get("outbox_stale_recovered") or 0) >= 1
        assert "outbox_dead" in summary


def test_run_reconciliation_mencatat_audit_event():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_reconcile_audit.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        _ = service.run_reconciliation()

        row = _query_one(
            db_path,
            """
            SELECT event_type, status
            FROM transaksi_persist_audit
            ORDER BY id DESC
            LIMIT 1
            """,
        )
        assert row is not None
        assert str(row["event_type"]) == "RECONCILIATION"
        assert str(row["status"]) == "ok"


def test_purge_retention_membersihkan_idempotency_audit_approval_dan_outbox_terminal():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_retention.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        outbox = FreeProdukSyncOutboxService(db_path=db_path)

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        cur.execute(
            """
            INSERT INTO transaksi_idempotency (
                idempotency_key, payload_hash, status, transaksi_id,
                trace_id, error_code, error_reason, created_at, updated_at
            )
            VALUES (?, ?, 'SUCCESS', 100, ?, NULL, NULL, ?, ?)
            """,
            (
                "trx-retention-success",
                "hash-retention-success",
                "trace-retention",
                "2000-01-01 00:00:00",
                "2000-01-01 00:00:00",
            ),
        )
        cur.execute(
            """
            INSERT INTO transaksi_persist_audit (
                event_dtime, trace_id, idempotency_key, event_type,
                transaksi_id, status, error_code, reason, payload_json, prev_hash, row_hash
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                "2000-01-01 00:00:00",
                "trace-retention",
                "trx-retention-success",
                "SUCCESS",
                100,
                "ok",
                "",
                "",
                "{}",
                "",
                "audit-hash-1",
            ),
        )
        cur.execute(
            """
            INSERT INTO transaksi_approval_trail (
                event_dtime, action_name, actor_name, approval_name,
                approval_status, trace_id, payload_json, prev_hash, row_hash
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                "2000-01-01 00:00:00",
                "approve-retention",
                "kasir-a",
                "admin-a",
                "approved",
                "trace-retention",
                "{}",
                "",
                "approval-hash-1",
            ),
        )
        conn.commit()
        conn.close()

        sent = outbox.enqueue_payload([{"transaksi_id": 90}], reason="retention_sent")
        dead = outbox.enqueue_payload([{"transaksi_id": 91}], reason="retention_dead")
        sent_id = int(sent.get("id") or 0)
        dead_id = int(dead.get("id") or 0)
        assert sent_id > 0
        assert dead_id > 0
        outbox.mark_sent(sent_id)
        outbox.mark_dead(dead_id, attempt_count=2, error_code="FAIL", error_message="fail")

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        cur.execute(
            """
            UPDATE free_produk_sync_outbox
            SET created_at = '2000-01-01 00:00:00',
                updated_at = '2000-01-01 00:00:00'
            WHERE id IN (?, ?)
            """,
            (sent_id, dead_id),
        )
        conn.commit()
        conn.close()

        summary = service.purge_retention(
            idempotency_days=1,
            audit_days=1,
            approval_days=1,
            outbox_days=1,
            purge_limit=500,
        )
        assert int(summary.get("idempotency_purged") or 0) >= 1
        assert int(summary.get("persist_audit_purged") or 0) >= 1
        assert int(summary.get("approval_trail_purged") or 0) >= 1
        assert int(summary.get("outbox_purged_sent") or 0) >= 1
        assert int(summary.get("outbox_purged_dead") or 0) >= 1

        conn = sqlite3.connect(db_path)
        cur = conn.cursor()
        cur.execute(
            """
            SELECT name
            FROM sqlite_master
            WHERE type = 'trigger'
              AND name IN (
                'trg_transaksi_persist_audit_immutable_update',
                'trg_transaksi_persist_audit_immutable_delete',
                'trg_transaksi_approval_trail_immutable_update',
                'trg_transaksi_approval_trail_immutable_delete'
              )
            """
        )
        trigger_names = {str(row[0]) for row in (cur.fetchall() or [])}
        conn.close()
        assert "trg_transaksi_persist_audit_immutable_update" in trigger_names
        assert "trg_transaksi_persist_audit_immutable_delete" in trigger_names
        assert "trg_transaksi_approval_trail_immutable_update" in trigger_names
        assert "trg_transaksi_approval_trail_immutable_delete" in trigger_names


def test_run_reconciliation_dengan_retention_policy_menyertakan_ringkasan_retention():
    with tempfile.TemporaryDirectory() as td:
        db_path = str(Path(td) / "enterprise_reconcile_retention.db")
        service = TransaksiEnterpriseControlService(db_path=db_path)
        summary = service.run_reconciliation(
            retention_policy={
                "enabled": True,
                "idempotency_days": 1,
                "audit_days": 1,
                "approval_days": 1,
                "outbox_days": 1,
                "limit": 50,
            }
        )

        retention = summary.get("retention")
        assert isinstance(retention, dict)
        assert "idempotency_purged" in retention
        assert "persist_audit_purged" in retention
        assert "approval_trail_purged" in retention
        assert "outbox_purged_sent" in retention
        assert "outbox_purged_dead" in retention
