# edited by glg
import os
import sqlite3
import tempfile

import pytest
import requests

from pypos.modules.sinkronisasi.models.export_upload_model import ExportUploadModel
from pypos.modules.sinkronisasi.services.export_upload_service import ExportUploadService
from pypos.modules.sinkronisasi.services.settlement_direct_service import SettlementDirectService

pytestmark = [pytest.mark.unit]


class _RecoveringUploadApiStub:
    def __init__(self):
        self.calls = 0

    def upload_file(self, file_path, metadata, timeout):
        _ = (file_path, metadata, timeout)
        self.calls += 1
        if self.calls == 1:
            return {
                "ok": False,
                "error": "network_timeout",
                "retryable": True,
                "status_code": 0,
                "response_text": "",
            }
        return {
            "ok": True,
            "error": "",
            "retryable": False,
            "status_code": 200,
            "response_text": '{"status":1,"idempotency_key":"NET-RECOVER-1"}',
        }

    def fetch_compile_status(self, idempotency_key, timeout):
        _ = (idempotency_key, timeout)
        return {
            "ok": True,
            "error": "",
            "retryable": False,
            "status_code": 200,
            "response_text": '{"status":1}',
            "response_json": {"status": 1},
        }


class _ExportPayloadStub:
    def __init__(self, payload):
        self.payload = payload

    def build_direct_settlement_payload(self, transaksi_ids=None, settlement_counter=""):
        _ = (transaksi_ids, settlement_counter)
        return dict(self.payload)


class _DummyResponse:
    def __init__(self, status_code=200, payload=None):
        self.status_code = int(status_code)
        self._payload = payload if isinstance(payload, dict) else {}
        self.text = "{}"

    def json(self):
        return dict(self._payload)


def _prepare_export_flux_table(db_path):
    conn = sqlite3.connect(str(db_path))
    try:
        cur = conn.cursor()
        cur.execute(
            """
            CREATE TABLE export_flux (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                batch_start TEXT NOT NULL,
                batch_end TEXT NOT NULL,
                table_name TEXT NULL,
                server_hash TEXT NULL,
                file_seq INTEGER NULL,
                file_hash TEXT NULL,
                file_size INTEGER NULL,
                row_count INTEGER NOT NULL DEFAULT 0,
                status TEXT NOT NULL DEFAULT 'PENDING',
                file_path TEXT NULL,
                error_log TEXT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
            """
        )
        conn.commit()
    finally:
        conn.close()


def _insert_success_flux(db_path, file_path):
    conn = sqlite3.connect(str(db_path))
    try:
        cur = conn.cursor()
        cur.execute(
            """
            INSERT INTO export_flux (
                batch_start, batch_end, table_name, server_hash, file_seq, file_hash, file_size,
                row_count, status, file_path, error_log, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
            """,
            (
                "0-10",
                "2026-04-13 10:00:00",
                "transaksi",
                "server-hash",
                1,
                "hash-1",
                123,
                10,
                "SUCCESS",
                str(file_path),
                "",
            ),
        )
        conn.commit()
    finally:
        conn.close()


def test_export_upload_recovery_retry_then_success():
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = os.path.join(tmpdir, "upload_recovery.db")
        file_path = os.path.join(tmpdir, "payload.gz")
        with open(file_path, "wb") as fh:
            fh.write(b"gzip-data")

        _prepare_export_flux_table(db_path)
        _insert_success_flux(db_path, file_path)

        model = ExportUploadModel(db_path=db_path)
        api = _RecoveringUploadApiStub()
        service = ExportUploadService(
            model=model,
            api_service=api,
            enabled_getter=lambda: True,
            batch_limit_getter=lambda: 10,
            timeout_getter=lambda: 5,
            max_attempt_getter=lambda: 5,
            backoff_base_getter=lambda: 1,
            backoff_factor_getter=lambda: 1,
            backoff_max_getter=lambda: 1,
            retention_days_getter=lambda: 0,
            compile_check_enabled_getter=lambda: False,
            allowed_export_tables_getter=lambda: ["transaksi"],
        )

        first = service.process_pending_uploads()
        assert int(first.get("retried") or 0) == 1
        assert int(first.get("uploaded") or 0) == 0

        conn = sqlite3.connect(db_path)
        try:
            conn.execute(
                "UPDATE export_flux SET upload_next_retry_at = ? WHERE id = 1",
                ("2000-01-01 00:00:00",),
            )
            conn.commit()
        finally:
            conn.close()

        second = service.process_pending_uploads()
        assert int(second.get("uploaded") or 0) == 1
        assert api.calls == 2

        conn = sqlite3.connect(db_path)
        try:
            cur = conn.cursor()
            cur.execute(
                "SELECT upload_status, upload_attempt_count FROM export_flux WHERE id = 1"
            )
            row = cur.fetchone()
        finally:
            conn.close()
        assert str(row[0]) == "UPLOADED"
        assert int(row[1]) >= 1


def test_settlement_direct_recovery_network_error_then_success():
    payload = {
        "user_id": 188,
        "dtime": "2026-04-13 11:00:00",
        "cabang_id": 100,
        "device_id": "118933446257318",
        "data": {"2026-04-13": {"id_penjualan": [1, 2]}},
    }
    service = SettlementDirectService(
        export_service=_ExportPayloadStub(payload=payload),
        endpoint_config_reader=lambda: {
            "api_base_url": "https://example.com",
            "ep_settlement_direct": "/settlement",
        },
        enabled_getter=lambda: True,
        auth_required_getter=lambda: False,
        timeout_getter=lambda: 3,
        retry_max_getter=lambda: 1,
        retry_backoff_getter=lambda: 0.0,
    )

    state = {"calls": 0}

    def _request_with_recovery(*args, **kwargs):
        _ = (args, kwargs)
        state["calls"] += 1
        if state["calls"] == 1:
            raise requests.exceptions.ConnectionError("temporary network outage")
        return _DummyResponse(status_code=200, payload={"success": True, "message": "ok"})

    service.request_with_retry = _request_with_recovery

    first = service.send_settlement({"counter": "ST-RECOVER-001", "transaksi_ids": [1, 2]})
    second = service.send_settlement({"counter": "ST-RECOVER-001", "transaksi_ids": [1, 2]})

    assert first["attempted"] is True
    assert first["ok"] is False
    assert first["retryable"] is True

    assert second["attempted"] is True
    assert second["ok"] is True
    assert int(second.get("status_code") or 0) == 200
