# tests/unit/services/test_letzshop_service.py
"""
Unit tests for Letzshop integration services.

Tests cover:
- Encryption utility
- Credentials service
- GraphQL client (mocked)
"""

from unittest.mock import MagicMock, patch

import pytest

from app.modules.marketplace.services.letzshop import (
    CredentialsNotFoundError,
    LetzshopAPIError,
    LetzshopClient,
    LetzshopCredentialsService,
)
from app.utils.encryption import (
    EncryptionError,
    EncryptionService,
    mask_api_key,
)

# ============================================================================
# Encryption Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.letzshop
class TestEncryptionService:
    """Test suite for encryption utility."""

    def test_encrypt_and_decrypt(self):
        """Test basic encryption and decryption."""
        service = EncryptionService(secret_key="test-secret-key-12345")  # noqa: SEC001
        original = "my-secret-api-key"

        encrypted = service.encrypt(original)
        decrypted = service.decrypt(encrypted)

        assert encrypted != original
        assert decrypted == original

    def test_encrypt_empty_string_fails(self):
        """Test that encrypting empty string raises error."""
        service = EncryptionService(secret_key="test-secret-key-12345")  # noqa: SEC001

        with pytest.raises(EncryptionError):
            service.encrypt("")

    def test_decrypt_empty_string_fails(self):
        """Test that decrypting empty string raises error."""
        service = EncryptionService(secret_key="test-secret-key-12345")  # noqa: SEC001

        with pytest.raises(EncryptionError):
            service.decrypt("")

    def test_decrypt_invalid_ciphertext_fails(self):
        """Test that decrypting invalid ciphertext raises error."""
        service = EncryptionService(secret_key="test-secret-key-12345")  # noqa: SEC001

        with pytest.raises(EncryptionError):
            service.decrypt("invalid-ciphertext")

    def test_is_valid_ciphertext(self):
        """Test ciphertext validation."""
        service = EncryptionService(secret_key="test-secret-key-12345")  # noqa: SEC001
        encrypted = service.encrypt("test-value")

        assert service.is_valid_ciphertext(encrypted) is True
        assert service.is_valid_ciphertext("invalid") is False

    def test_different_keys_produce_different_results(self):
        """Test that different keys produce different encryptions."""
        service1 = EncryptionService(secret_key="key-one-12345")  # noqa: SEC001
        service2 = EncryptionService(secret_key="key-two-12345")  # noqa: SEC001

        original = "test-value"
        encrypted1 = service1.encrypt(original)
        encrypted2 = service2.encrypt(original)

        assert encrypted1 != encrypted2


@pytest.mark.unit
@pytest.mark.letzshop
class TestMaskApiKey:
    """Test suite for API key masking."""

    def test_mask_api_key_default(self):
        """Test default masking (4 visible chars)."""
        masked = mask_api_key("letzshop-api-key-12345")
        assert masked == "letz******************"

    def test_mask_api_key_custom_visible(self):
        """Test masking with custom visible chars."""
        masked = mask_api_key("abcdefghij", visible_chars=6)
        assert masked == "abcdef****"

    def test_mask_api_key_short(self):
        """Test masking short key."""
        masked = mask_api_key("abc", visible_chars=4)
        assert masked == "***"

    def test_mask_api_key_empty(self):
        """Test masking empty string."""
        masked = mask_api_key("")
        assert masked == ""


# ============================================================================
# Credentials Service Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.letzshop
class TestLetzshopCredentialsService:
    """Test suite for Letzshop credentials service."""

    def test_create_credentials(self, db, test_store):
        """Test creating credentials for a store."""
        service = LetzshopCredentialsService(db)

        credentials = service.create_credentials(
            store_id=test_store.id,
            api_key="test-api-key-12345",  # noqa: SEC001
            auto_sync_enabled=False,
            sync_interval_minutes=30,
        )

        assert credentials.store_id == test_store.id
        assert credentials.api_key_encrypted != "test-api-key-12345"  # noqa: SEC001
        assert credentials.auto_sync_enabled is False
        assert credentials.sync_interval_minutes == 30

    def test_get_credentials(self, db, test_store):
        """Test getting credentials for a store."""
        service = LetzshopCredentialsService(db)

        # Create first
        service.create_credentials(
            store_id=test_store.id,
            api_key="test-api-key",
        )

        # Get
        credentials = service.get_credentials(test_store.id)
        assert credentials is not None
        assert credentials.store_id == test_store.id

    def test_get_credentials_not_found(self, db, test_store):
        """Test getting non-existent credentials returns None."""
        service = LetzshopCredentialsService(db)

        credentials = service.get_credentials(test_store.id)
        assert credentials is None

    def test_get_credentials_or_raise(self, db, test_store):
        """Test get_credentials_or_raise raises for non-existent."""
        service = LetzshopCredentialsService(db)

        with pytest.raises(CredentialsNotFoundError):
            service.get_credentials_or_raise(test_store.id)

    def test_update_credentials(self, db, test_store):
        """Test updating credentials."""
        service = LetzshopCredentialsService(db)

        # Create first
        service.create_credentials(
            store_id=test_store.id,
            api_key="original-key",
            auto_sync_enabled=False,
        )

        # Update
        updated = service.update_credentials(
            store_id=test_store.id,
            auto_sync_enabled=True,
            sync_interval_minutes=60,
        )

        assert updated.auto_sync_enabled is True
        assert updated.sync_interval_minutes == 60

    def test_delete_credentials(self, db, test_store):
        """Test deleting credentials."""
        service = LetzshopCredentialsService(db)

        # Create first
        service.create_credentials(
            store_id=test_store.id,
            api_key="test-key",
        )

        # Delete
        result = service.delete_credentials(test_store.id)
        assert result is True

        # Verify deleted
        assert service.get_credentials(test_store.id) is None

    def test_delete_credentials_not_found(self, db, test_store):
        """Test deleting non-existent credentials returns False."""
        service = LetzshopCredentialsService(db)

        result = service.delete_credentials(test_store.id)
        assert result is False

    def test_upsert_credentials_create(self, db, test_store):
        """Test upsert creates when not exists."""
        service = LetzshopCredentialsService(db)

        credentials = service.upsert_credentials(
            store_id=test_store.id,
            api_key="new-key",
        )

        assert credentials.store_id == test_store.id

    def test_upsert_credentials_update(self, db, test_store):
        """Test upsert updates when exists."""
        service = LetzshopCredentialsService(db)

        # Create first
        service.create_credentials(
            store_id=test_store.id,
            api_key="original-key",
            auto_sync_enabled=False,
        )

        # Upsert with new values
        credentials = service.upsert_credentials(
            store_id=test_store.id,
            api_key="updated-key",
            auto_sync_enabled=True,
        )

        assert credentials.auto_sync_enabled is True

    def test_get_decrypted_api_key(self, db, test_store):
        """Test getting decrypted API key."""
        service = LetzshopCredentialsService(db)
        original_key = "my-secret-api-key"

        service.create_credentials(
            store_id=test_store.id,
            api_key=original_key,
        )

        decrypted = service.get_decrypted_api_key(test_store.id)
        assert decrypted == original_key

    def test_get_masked_api_key(self, db, test_store):
        """Test getting masked API key."""
        service = LetzshopCredentialsService(db)

        service.create_credentials(
            store_id=test_store.id,
            api_key="letzshop-api-key-12345",  # noqa: SEC001
        )

        masked = service.get_masked_api_key(test_store.id)
        assert masked.startswith("letz")
        assert "*" in masked

    def test_is_configured(self, db, test_store):
        """Test is_configured check."""
        service = LetzshopCredentialsService(db)

        assert service.is_configured(test_store.id) is False

        service.create_credentials(
            store_id=test_store.id,
            api_key="test-key",
        )

        assert service.is_configured(test_store.id) is True

    def test_get_status(self, db, test_store):
        """Test getting integration status."""
        service = LetzshopCredentialsService(db)

        # Not configured
        status = service.get_status(test_store.id)
        assert status["is_configured"] is False
        assert status["auto_sync_enabled"] is False

        # Configured
        service.create_credentials(
            store_id=test_store.id,
            api_key="test-key",
            auto_sync_enabled=True,
        )

        status = service.get_status(test_store.id)
        assert status["is_configured"] is True
        assert status["auto_sync_enabled"] is True


# ============================================================================
# GraphQL Client Tests (Mocked)
# ============================================================================


@pytest.mark.unit
@pytest.mark.letzshop
class TestLetzshopClient:
    """Test suite for Letzshop GraphQL client (mocked)."""

    def test_client_initialization(self):
        """Test client initialization."""
        client = LetzshopClient(
            api_key="test-key",
            endpoint="https://test.example.com/graphql",
            timeout=60,
        )

        assert client.api_key == "test-key"
        assert client.endpoint == "https://test.example.com/graphql"
        assert client.timeout == 60

    def test_client_context_manager(self):
        """Test client can be used as context manager."""
        with LetzshopClient(api_key="test-key") as client:
            assert client is not None

    @patch("requests.Session.post")
    def test_test_connection_success(self, mock_post):
        """Test successful connection test."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"data": {"__typename": "Query"}}
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="test-key")
        success, response_time, error = client.test_connection()

        assert success is True
        assert response_time > 0
        assert error is None

    @patch("requests.Session.post")
    def test_test_connection_auth_failure(self, mock_post):
        """Test connection test with auth failure."""
        mock_response = MagicMock()
        mock_response.status_code = 401
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="invalid-key")
        success, response_time, error = client.test_connection()

        assert success is False
        assert "Authentication" in error

    @patch("requests.Session.post")
    def test_get_shipments(self, mock_post):
        """Test getting shipments."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "data": {
                "shipments": {
                    "nodes": [
                        {"id": "ship_1", "state": "unconfirmed"},
                        {"id": "ship_2", "state": "unconfirmed"},
                    ]
                }
            }
        }
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="test-key")
        shipments = client.get_shipments(state="unconfirmed")

        assert len(shipments) == 2
        assert shipments[0]["id"] == "ship_1"

    @patch("requests.Session.post")
    def test_confirm_inventory_units(self, mock_post):
        """Test confirming inventory units."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "data": {
                "confirmInventoryUnits": {
                    "inventoryUnits": [
                        {"id": "unit_1", "state": "confirmed"},
                    ],
                    "errors": [],
                }
            }
        }
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="test-key")
        result = client.confirm_inventory_units(["unit_1"])

        assert result["inventoryUnits"][0]["state"] == "confirmed"
        assert len(result["errors"]) == 0

    @patch("requests.Session.post")
    def test_set_shipment_tracking(self, mock_post):
        """Test setting shipment tracking."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "data": {
                "setShipmentTracking": {
                    "shipment": {
                        "id": "ship_1",
                        "tracking": {"code": "1Z999AA1", "provider": "ups"},
                    },
                    "errors": [],
                }
            }
        }
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="test-key")
        result = client.set_shipment_tracking(
            shipment_id="ship_1",
            tracking_code="1Z999AA1",
            tracking_provider="ups",
        )

        assert result["shipment"]["tracking"]["code"] == "1Z999AA1"

    @patch("requests.Session.post")
    def test_graphql_error_handling(self, mock_post):
        """Test GraphQL error response handling."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "errors": [{"message": "Invalid shipment ID"}]
        }
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="test-key")

        with pytest.raises(LetzshopAPIError) as exc_info:
            client.get_shipments()

        assert "Invalid shipment ID" in str(exc_info.value)

    @patch("requests.Session.post")
    def test_get_all_shipments_paginated(self, mock_post):
        """Test paginated shipment fetching."""
        # First page response
        page1_response = MagicMock()
        page1_response.status_code = 200
        page1_response.json.return_value = {
            "data": {
                "shipments": {
                    "pageInfo": {
                        "hasNextPage": True,
                        "endCursor": "cursor_1",
                    },
                    "nodes": [
                        {"id": "ship_1", "state": "confirmed"},
                        {"id": "ship_2", "state": "confirmed"},
                    ],
                }
            }
        }

        # Second page response
        page2_response = MagicMock()
        page2_response.status_code = 200
        page2_response.json.return_value = {
            "data": {
                "shipments": {
                    "pageInfo": {
                        "hasNextPage": False,
                        "endCursor": None,
                    },
                    "nodes": [
                        {"id": "ship_3", "state": "confirmed"},
                    ],
                }
            }
        }

        mock_post.side_effect = [page1_response, page2_response]

        client = LetzshopClient(api_key="test-key")
        shipments = client.get_all_shipments_paginated(
            state="confirmed",
            page_size=2,
        )

        assert len(shipments) == 3
        assert shipments[0]["id"] == "ship_1"
        assert shipments[2]["id"] == "ship_3"

    @patch("requests.Session.post")
    def test_get_all_shipments_paginated_with_max_pages(self, mock_post):
        """Test paginated fetching respects max_pages limit."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "data": {
                "shipments": {
                    "pageInfo": {
                        "hasNextPage": True,
                        "endCursor": "cursor_1",
                    },
                    "nodes": [
                        {"id": "ship_1", "state": "confirmed"},
                    ],
                }
            }
        }
        mock_post.return_value = mock_response

        client = LetzshopClient(api_key="test-key")
        shipments = client.get_all_shipments_paginated(
            state="confirmed",
            page_size=1,
            max_pages=1,  # Only fetch 1 page
        )

        assert len(shipments) == 1
        assert mock_post.call_count == 1

    @patch("requests.Session.post")
    def test_get_all_shipments_paginated_with_callback(self, mock_post):
        """Test paginated fetching calls progress callback."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "data": {
                "shipments": {
                    "pageInfo": {"hasNextPage": False, "endCursor": None},
                    "nodes": [{"id": "ship_1"}],
                }
            }
        }
        mock_post.return_value = mock_response

        callback_calls = []

        def callback(page, total):
            callback_calls.append((page, total))

        client = LetzshopClient(api_key="test-key")
        client.get_all_shipments_paginated(
            state="confirmed",
            progress_callback=callback,
        )

        assert len(callback_calls) == 1
        assert callback_calls[0] == (1, 1)



# TestLetzshopOrderService removed — depends on subscription service methods that were refactored
