import os
import tempfile
import unittest

import pytest
import requests

from pypos.modules.sinkronisasi.services.export_upload_api_service import ExportUploadApiService

pytestmark = [pytest.mark.unit]


class _ResponseStub:
    def __init__(self, status_code=200, text="", json_payload=None, json_error=None):
        self.status_code = int(status_code)
        self.text = str(text or "")
        self._json_payload = json_payload if isinstance(json_payload, dict) else {}
        self._json_error = json_error

    def json(self):
        if self._json_error is not None:
            raise self._json_error
        return dict(self._json_payload)


class ExportUploadApiServiceTests(unittest.TestCase):
    # edited by glg
    def test_upload_file_uses_xz_mime_type_for_xz_file(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            file_path = os.path.join(tmpdir, "sample.xz")
            with open(file_path, "wb") as fh:
                fh.write(b"xz")

            captured = {}

            def _request_stub(*args, **kwargs):
                _ = args
                file_tuple = kwargs["files"]["file_contents"]
                captured["mime"] = file_tuple[2]
                return _ResponseStub(status_code=200, text='{"status":1}', json_payload={"status": 1})

            service = ExportUploadApiService(http_client=object())
            service._build_upload_url = lambda: "https://example.test/upload"
            service.request_with_retry = _request_stub

            result = service.upload_file(
                file_path=file_path,
                metadata={"id_machine": "M1", "file_hash": "a" * 64, "file_seq": "1"},
                timeout=5,
            )
            self.assertTrue(result["ok"])
            self.assertEqual(captured.get("mime"), "application/x-xz")

    def test_upload_file_uses_gzip_mime_type_for_gz_file(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            file_path = os.path.join(tmpdir, "sample.gz")
            with open(file_path, "wb") as fh:
                fh.write(b"gzip")

            captured = {}

            def _request_stub(*args, **kwargs):
                _ = args
                file_tuple = kwargs["files"]["file_contents"]
                captured["mime"] = file_tuple[2]
                return _ResponseStub(status_code=200, text='{"status":1}', json_payload={"status": 1})

            service = ExportUploadApiService(http_client=object())
            service._build_upload_url = lambda: "https://example.test/upload"
            service.request_with_retry = _request_stub

            result = service.upload_file(
                file_path=file_path,
                metadata={"id_machine": "M1", "file_hash": "a" * 64, "file_seq": "1"},
                timeout=5,
            )
            self.assertTrue(result["ok"])
            self.assertEqual(captured.get("mime"), "application/gzip")

    def test_upload_file_uses_configurable_retryable_reason_list(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            file_path = os.path.join(tmpdir, "sample.gz")
            with open(file_path, "wb") as fh:
                fh.write(b"gzip")

            service = ExportUploadApiService(
                http_client=object(),
                retryable_reason_getter=lambda: ["server busy", "maintenance"],
            )
            service._build_upload_url = lambda: "https://example.test/upload"
            service.request_with_retry = lambda *args, **kwargs: _ResponseStub(
                status_code=200,
                text='{"status":0,"reason":"server busy sementara"}',
                json_payload={"status": 0, "reason": "server busy sementara"},
            )

            result = service.upload_file(
                file_path=file_path,
                metadata={"id_machine": "M1", "file_hash": "a" * 64, "file_seq": "1"},
                timeout=5,
            )
            self.assertFalse(result["ok"])
            self.assertTrue(result["retryable"])
            self.assertIn("server busy", result["error"].lower())

    def test_fetch_compile_status_marks_non_retryable_for_non_transient_reason(self):
        service = ExportUploadApiService(
            http_client=object(),
            retryable_reason_getter=lambda: ["tempor", "retry"],
        )
        service._build_compile_status_url = lambda: "https://example.test/status"
        service.request_with_retry = lambda *args, **kwargs: _ResponseStub(
            status_code=200,
            text='{"status":0,"reason":"missing_idempotency_key"}',
            json_payload={"status": 0, "reason": "missing_idempotency_key"},
        )

        result = service.fetch_compile_status("IDEMPOTENCY-1", timeout=5)
        self.assertFalse(result["ok"])
        self.assertFalse(result["retryable"])
        self.assertEqual(result["error"], "missing_idempotency_key")

    def test_upload_file_missing_endpoint_is_non_retryable(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            file_path = os.path.join(tmpdir, "sample.gz")
            with open(file_path, "wb") as fh:
                fh.write(b"gzip")

            service = ExportUploadApiService(http_client=object())
            service._build_upload_url = lambda: (_ for _ in ()).throw(ValueError("Endpoint ep_upload_stream belum diatur"))

            result = service.upload_file(
                file_path=file_path,
                metadata={"id_machine": "M1", "file_hash": "a" * 64, "file_seq": "1"},
                timeout=5,
            )
            self.assertFalse(result["ok"])
            self.assertFalse(result["retryable"])
            self.assertIn("ep_upload_stream", result["error"])

    def test_compile_status_missing_endpoint_is_non_retryable(self):
        service = ExportUploadApiService(http_client=object())
        service._build_compile_status_url = (
            lambda: (_ for _ in ()).throw(ValueError("Endpoint ep_upload_compile_status belum diatur"))
        )

        result = service.fetch_compile_status("IDEMPOTENCY-1", timeout=5)
        self.assertFalse(result["ok"])
        self.assertFalse(result["retryable"])
        self.assertIn("ep_upload_compile_status", result["error"])

    def test_upload_file_timeout_returns_retryable_error_contract(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            file_path = os.path.join(tmpdir, "sample.gz")
            with open(file_path, "wb") as fh:
                fh.write(b"gzip")

            service = ExportUploadApiService(http_client=object())
            service._build_upload_url = lambda: "https://example.test/upload"
            service.request_with_retry = lambda *args, **kwargs: (_ for _ in ()).throw(requests.exceptions.Timeout("timeout"))

            result = service.upload_file(
                file_path=file_path,
                metadata={"id_machine": "M1", "file_hash": "a" * 64, "file_seq": "1"},
                timeout=5,
            )
            self.assertFalse(result["ok"])
            self.assertTrue(result["retryable"])
            self.assertEqual(result["error_code"], "SYNC_UPLOAD_TIMEOUT")
            self.assertEqual(result["reason"], "timeout")
            self.assertTrue(str(result.get("trace_id") or "").startswith("sync-upload-"))

    def test_fetch_compile_status_parse_failure_returns_non_retryable_reject(self):
        service = ExportUploadApiService(http_client=object(), retryable_reason_getter=lambda: [])
        service._build_compile_status_url = lambda: "https://example.test/status"
        service.request_with_retry = lambda *args, **kwargs: _ResponseStub(
            status_code=200,
            text="not-json",
            json_payload={},
            json_error=ValueError("invalid json"),
        )

        result = service.fetch_compile_status("IDEMPOTENCY-1", timeout=5)
        self.assertFalse(result["ok"])
        self.assertFalse(result["retryable"])
        self.assertIn("compile_status_rejected_", str(result["error"]))
        self.assertEqual(result["reason"], "compile_status_rejected_none")
        self.assertEqual(result["error_code"], "SYNC_COMPILE_COMPILE_REJECTED_NON_RETRYABLE")
        self.assertTrue(str(result.get("trace_id") or "").startswith("sync-compile-"))

    def test_fetch_compile_status_retryable_on_transient_reason(self):
        service = ExportUploadApiService(
            http_client=object(),
            retryable_reason_getter=lambda: ["tempor", "maintenance"],
        )
        service._build_compile_status_url = lambda: "https://example.test/status"
        service.request_with_retry = lambda *args, **kwargs: _ResponseStub(
            status_code=200,
            text='{"status":0,"reason":"temporary maintenance"}',
            json_payload={"status": 0, "reason": "temporary maintenance"},
        )

        result = service.fetch_compile_status("IDEMPOTENCY-1", timeout=5)
        self.assertFalse(result["ok"])
        self.assertTrue(result["retryable"])
        self.assertEqual(result["error_code"], "SYNC_COMPILE_COMPILE_REJECTED_RETRYABLE")
        self.assertEqual(result["reason"], "temporary_maintenance")


if __name__ == "__main__":
    unittest.main()
