"""Unit tests for Facturapi service with mocked HTTP calls.

These tests do not require PostgreSQL or network access.
"""

import base64
from unittest import mock

import pytest

# Import must not trigger DB connections.
from pos.services import facturapi_service


@pytest.fixture
def user_key():
    """Patch USER_KEY for the duration of the test."""
    with mock.patch.object(facturapi_service, "USER_KEY", "sk_user_abc"):
        yield


class TestGetApiKey:
    def test_prefers_secret_key(self):
        config = {
            "facturapi_secret_key": "sk_secret_123",
            "facturapi_key": "sk_test_456",
        }
        assert facturapi_service.get_api_key(config) == "sk_secret_123"

    def test_falls_back_to_facturapi_key(self):
        config = {"facturapi_key": "sk_test_456"}
        assert facturapi_service.get_api_key(config) == "sk_test_456"

    def test_falls_back_to_cfdi_prefixed_key(self):
        config = {"cfdi_facturapi_key": "sk_test_789"}
        assert facturapi_service.get_api_key(config) == "sk_test_789"

    def test_falls_back_to_user_key_env(self, user_key):
        assert facturapi_service.get_api_key({}) == "sk_user_abc"

    def test_raises_when_nothing_configured(self):
        with (
            mock.patch.object(facturapi_service, "USER_KEY", ""),
            pytest.raises(facturapi_service.FacturapiError),
        ):
            facturapi_service.get_api_key({})


class TestGetOrgId:
    def test_prefers_short_key(self):
        config = {"facturapi_org_id": "org_123", "cfdi_facturapi_org_id": "org_456"}
        assert facturapi_service._get_org_id(config) == "org_123"

    def test_falls_back_to_cfdi_prefixed_key(self):
        config = {"cfdi_facturapi_org_id": "org_789"}
        assert facturapi_service._get_org_id(config) == "org_789"

    def test_returns_none_when_missing(self):
        assert facturapi_service._get_org_id({}) is None


class TestRequest:
    @mock.patch("pos.services.facturapi_service.requests.request")
    def test_successful_request_returns_json(self, mock_request):
        mock_response = mock.Mock()
        mock_response.ok = True
        mock_response.status_code = 200
        mock_response.content = b'{"id": "inv_1"}'
        mock_response.json.return_value = {"id": "inv_1"}
        mock_request.return_value = mock_response

        result = facturapi_service._request("GET", "/invoices", "sk_test")

        assert result == {"id": "inv_1"}
        mock_request.assert_called_once()
        _, kwargs = mock_request.call_args
        assert kwargs["auth"] == ("sk_test", "")

    @mock.patch("pos.services.facturapi_service.requests.request")
    def test_failed_request_raises_facturapi_error(self, mock_request):
        mock_response = mock.Mock()
        mock_response.ok = False
        mock_response.status_code = 400
        mock_response.text = "Bad request"
        mock_request.return_value = mock_response

        with pytest.raises(facturapi_service.FacturapiError) as exc_info:
            facturapi_service._request("POST", "/invoices", "sk_test", json_payload={})

        assert exc_info.value.status_code == 400


class TestCreateOrganization:
    @mock.patch("pos.services.facturapi_service.find_organization_by_rfc")
    @mock.patch("pos.services.facturapi_service._request")
    def test_creates_organization_and_generates_live_key(self, mock_request, mock_find, user_key):
        mock_find.return_value = None
        mock_request.side_effect = [
            {"id": "org_123"},  # POST /organizations
            {"key": "sk_live_abc"},  # PUT /organizations/org_123/apikeys/live
        ]

        result = facturapi_service.create_organization({"rfc": "ABC010101AAA", "razon_social": "Test SA"})

        assert result == {"org_id": "org_123", "api_key": "sk_live_abc"}
        assert mock_request.call_count == 2

    @mock.patch("pos.services.facturapi_service.find_organization_by_rfc")
    @mock.patch("pos.services.facturapi_service._request")
    def test_reuses_existing_organization_by_rfc(self, mock_request, mock_find, user_key):
        mock_find.return_value = {"id": "org_existing"}
        mock_request.return_value = {"key": "sk_live_existing"}

        result = facturapi_service.create_organization({"rfc": "ABC010101AAA", "razon_social": "Test SA"})

        assert result["org_id"] == "org_existing"
        mock_request.assert_called_once()


class TestUploadCsd:
    @mock.patch("pos.services.facturapi_service.requests.post")
    def test_uploads_csd(self, mock_post):
        mock_response = mock.Mock()
        mock_response.ok = True
        mock_response.json.return_value = {"certificate": {"has_certificate": True}}
        mock_post.return_value = mock_response

        config = {"facturapi_key": "sk_test", "facturapi_org_id": "org_1"}
        cer_b64 = base64.b64encode(b"fake-cer").decode("ascii")
        key_b64 = base64.b64encode(b"fake-key").decode("ascii")

        result = facturapi_service.upload_csd(config, cer_b64, key_b64, "password")

        assert result["certificate"]["has_certificate"] is True
        mock_post.assert_called_once()
        _, kwargs = mock_post.call_args
        assert kwargs["auth"] == ("sk_test", "")


class TestCreateInvoice:
    @mock.patch("pos.services.facturapi_service._request")
    def test_creates_invoice(self, mock_request):
        mock_request.return_value = {"id": "inv_1", "uuid": "uuid-1"}

        config = {"facturapi_key": "sk_test"}
        payload = {"customer": {"tax_id": "XAXX010101000"}}

        result = facturapi_service.create_invoice(config, payload)

        assert result["uuid"] == "uuid-1"
        mock_request.assert_called_once_with("POST", "/invoices", "sk_test", json_payload=payload, timeout=90)


class TestCancelInvoice:
    @mock.patch("pos.services.facturapi_service._request")
    def test_cancel_invoice_with_replacement(self, mock_request):
        mock_request.return_value = {"status": "canceled"}

        config = {"facturapi_key": "sk_test"}
        result = facturapi_service.cancel_invoice(config, "inv_1", "01", replacement_uuid="uuid-2")

        assert result["status"] == "canceled"
        mock_request.assert_called_once_with(
            "DELETE",
            "/invoices/inv_1",
            "sk_test",
            params={"motive": "01", "replacement": "uuid-2"},
            timeout=60,
        )


class TestDownloadXml:
    @mock.patch("pos.services.facturapi_service.requests.request")
    def test_downloads_xml(self, mock_request):
        mock_response = mock.Mock()
        mock_response.ok = True
        mock_response.content = b"<xml/>"
        mock_request.return_value = mock_response

        config = {"facturapi_key": "sk_test"}
        result = facturapi_service.download_xml(config, "inv_1")

        assert result == b"<xml/>"


class TestGetOrgStatus:
    @mock.patch("pos.services.facturapi_service.get_organization")
    def test_returns_configured_with_csd(self, mock_get_org):
        mock_get_org.return_value = {
            "legal": {"name": "Test SA", "tax_id": "ABC010101AAA"},
            "certificate": {"has_certificate": True},
            "pending_steps": [],
        }

        config = {"facturapi_key": "sk_test", "cfdi_facturapi_org_id": "org_1"}
        result = facturapi_service.get_org_status(config)

        assert result["configured"] is True
        assert result["has_csd"] is True
        assert result["has_org_id"] is True

    def test_returns_error_without_key(self):
        with mock.patch.object(facturapi_service, "USER_KEY", ""):
            result = facturapi_service.get_org_status({})
        assert result["has_key"] is False
        assert "not configured" in result["error"].lower()

    def test_returns_error_without_org_id(self):
        with mock.patch.object(facturapi_service, "USER_KEY", ""):
            result = facturapi_service.get_org_status({"facturapi_key": "sk_test"})
        assert result["has_org_id"] is False
        assert "organization" in result["error"].lower()


class TestCreateOrUpdateCustomer:
    @mock.patch("pos.services.facturapi_service._request")
    def test_creates_new_customer_when_not_found(self, mock_request):
        mock_request.side_effect = [
            {"data": []},  # search
            {"id": "cus_1"},  # create
        ]

        config = {"facturapi_key": "sk_test"}
        customer_id = facturapi_service.create_or_update_customer(
            config,
            {
                "legal_name": "Test",
                "tax_id": "ABC010101AAA",
                "tax_system": "601",
                "email": "test@example.com",
                "zip": "01000",
            },
        )

        assert customer_id == "cus_1"
