"""
Tests for Route402Client
"""
import pytest
from unittest.mock import Mock, patch
from datetime import datetime
from route402 import Route402Client, Payment, PaymentStatus, Chain
from route402.exceptions import APIError, ValidationError, PaymentError


class TestRoute402Client:
    """Test suite for Route402Client"""

    @pytest.fixture
    def client(self):
        """Create test client"""
        return Route402Client(api_key="apk_test_key")

    @pytest.fixture
    def mock_payment_response(self):
        """Mock payment API response"""
        return {
            "payment": {
                "id": "pay_test123",
                "chainId": 8453,
                "buyer": "0x1234567890123456789012345678901234567890",
                "seller": "0x0987654321098765432109876543210987654321",
                "amount": "10.00",
                "feeBps": 50,
                "txHash": "0xabcdef1234567890",
                "status": "CONFIRMED",
                "reference": "test-order-123",
                "createdAt": "2025-01-15T10:00:00Z",
                "confirmedAt": "2025-01-15T10:00:25Z"
            }
        }

    def test_client_initialization(self):
        """Test client initialization"""
        client = Route402Client(api_key="apk_test")
        assert client.api_key == "apk_test"
        assert client.api_url == "https://api.route402.com"
        assert client.session.headers["x-api-key"] == "apk_test"

    def test_client_custom_url(self):
        """Test client with custom API URL"""
        client = Route402Client(api_key="apk_test", api_url="https://custom.api.com/")
        assert client.api_url == "https://custom.api.com"

    @patch("requests.Session.request")
    def test_create_payment_success(self, mock_request, client, mock_payment_response):
        """Test successful payment creation"""
        mock_request.return_value.status_code = 200
        mock_request.return_value.json.return_value = mock_payment_response

        payment = client.create_payment(
            seller="0x0987654321098765432109876543210987654321",
            amount="10.00",
            reference="test-order-123"
        )

        assert isinstance(payment, Payment)
        assert payment.id == "pay_test123"
        assert payment.amount == "10.00"
        assert payment.status == PaymentStatus.CONFIRMED
        assert payment.reference == "test-order-123"
        assert payment.fee_amount == 0.05  # 10 * 50 / 10000
        assert payment.net_amount == 9.95

    @patch("requests.Session.request")
    def test_create_payment_validation_error(self, mock_request, client):
        """Test payment creation with validation error"""
        mock_request.return_value.status_code = 400
        mock_request.return_value.json.return_value = {
            "error": "Invalid seller address"
        }

        with pytest.raises(ValidationError):
            client.create_payment(
                seller="invalid-address",
                amount="10.00"
            )

    @patch("requests.Session.request")
    def test_get_payment_success(self, mock_request, client, mock_payment_response):
        """Test get payment by ID"""
        mock_request.return_value.status_code = 200
        mock_request.return_value.json.return_value = mock_payment_response

        payment = client.get_payment("pay_test123")

        assert isinstance(payment, Payment)
        assert payment.id == "pay_test123"
        assert payment.status == PaymentStatus.CONFIRMED

    @patch("requests.Session.request")
    def test_get_payment_not_found(self, mock_request, client):
        """Test get payment with non-existent ID"""
        mock_request.return_value.status_code = 404
        mock_request.return_value.json.return_value = {
            "error": "Payment not found"
        }

        with pytest.raises(APIError) as exc_info:
            client.get_payment("pay_nonexistent")

        assert exc_info.value.status_code == 404

    @patch("requests.Session.request")
    def test_list_payments_success(self, mock_request, client, mock_payment_response):
        """Test list payments"""
        mock_request.return_value.status_code = 200
        mock_request.return_value.json.return_value = {
            "payments": [mock_payment_response["payment"]] * 3,
            "total": 3
        }

        payments = client.list_payments(limit=10)

        assert len(payments) == 3
        assert all(isinstance(p, Payment) for p in payments)
        assert payments[0].id == "pay_test123"

    @patch("requests.Session.request")
    def test_list_payments_with_filters(self, mock_request, client):
        """Test list payments with status filter"""
        mock_request.return_value.status_code = 200
        mock_request.return_value.json.return_value = {
            "payments": [],
            "total": 0
        }

        payments = client.list_payments(
            status=PaymentStatus.CONFIRMED,
            limit=50,
            offset=10
        )

        # Verify request was called with correct params
        call_args = mock_request.call_args
        assert call_args[1]["params"]["status"] == "CONFIRMED"
        assert call_args[1]["params"]["limit"] == 50
        assert call_args[1]["params"]["offset"] == 10

    @patch("requests.Session.request")
    def test_export_payments_csv(self, mock_request, client):
        """Test CSV export"""
        csv_content = "Date,Buyer,Amount,Fee,Net,TxHash,Status,Reference\n" \
                     "2025-01-15,0x123...,10.00,0.05,9.95,0xabc...,CONFIRMED,order-1\n"

        mock_request.return_value.status_code = 200
        mock_request.return_value.text = csv_content

        csv_data = client.export_payments_csv(
            start_date="2025-01-01",
            end_date="2025-01-31"
        )

        assert csv_data == csv_content
        assert "Date,Buyer,Amount" in csv_data

    @patch("requests.Session.request")
    def test_get_chains(self, mock_request, client):
        """Test get supported chains"""
        mock_request.return_value.status_code = 200
        mock_request.return_value.json.return_value = {
            "chains": [
                {
                    "chainId": 8453,
                    "name": "Base",
                    "router": "0x04D96657CEd5A8861E26412BE30C2D418000edBb",
                    "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                }
            ]
        }

        chains = client.get_chains()

        assert len(chains) == 1
        assert chains[0]["chainId"] == 8453
        assert chains[0]["name"] == "Base"

    @patch("requests.Session.request")
    def test_api_error_handling(self, mock_request, client):
        """Test API error handling"""
        mock_request.return_value.status_code = 500
        mock_request.return_value.json.return_value = {
            "error": "Internal server error"
        }

        with pytest.raises(APIError) as exc_info:
            client.get_payment("pay_test")

        assert exc_info.value.status_code == 500
        assert "Internal server error" in exc_info.value.message

    @patch("requests.Session.request")
    def test_timeout_handling(self, mock_request, client):
        """Test timeout handling"""
        import requests
        mock_request.side_effect = requests.Timeout()

        with pytest.raises(APIError) as exc_info:
            client.get_payment("pay_test")

        assert "timeout" in exc_info.value.message.lower()


class TestPaymentModel:
    """Test Payment model"""

    def test_payment_fee_calculation(self):
        """Test fee amount calculation"""
        payment = Payment(
            id="pay_test",
            chain_id=8453,
            buyer="0x123",
            seller="0x456",
            amount="100.00",
            fee_bps=50,  # 0.5%
            tx_hash="0xabc",
            status=PaymentStatus.CONFIRMED,
            reference=None,
            created_at=datetime.now()
        )

        assert payment.fee_amount == 0.5  # 100 * 50 / 10000
        assert payment.net_amount == 99.5

    def test_payment_status_checks(self):
        """Test payment status helper properties"""
        confirmed_payment = Payment(
            id="pay_test",
            chain_id=8453,
            buyer="0x123",
            seller="0x456",
            amount="10.00",
            fee_bps=50,
            tx_hash="0xabc",
            status=PaymentStatus.CONFIRMED,
            reference=None,
            created_at=datetime.now(),
            confirmed_at=datetime.now()
        )

        assert confirmed_payment.is_confirmed is True
        assert confirmed_payment.is_pending is False

        pending_payment = Payment(
            id="pay_test2",
            chain_id=8453,
            buyer="0x123",
            seller="0x456",
            amount="10.00",
            fee_bps=50,
            tx_hash=None,
            status=PaymentStatus.PENDING,
            reference=None,
            created_at=datetime.now()
        )

        assert pending_payment.is_confirmed is False
        assert pending_payment.is_pending is True
