"""
Tests for BIAPAY Python SDK
Run with: pytest tests/
"""

import hashlib
import hmac
import pytest

try:
    import responses as responses_lib
    HAS_RESPONSES = True
except ImportError:
    HAS_RESPONSES = False

from biapay import BIAPay, PaymentSession, CallbackPayload
from biapay.exceptions import (
    BIAPayAuthError,
    BIAPayConnectionError,
    BIAPaySignatureError,
    BIAPayValidationError,
    BIAPayError,
)

CLIENT_ID = "6ec5129d-4a8a-49d7-a10f-eb31caa1232b"
CLIENT_SECRET = "V059WR536h84h5357h3jzgh8nhw6h55g"
ACCESS_TOKEN = "eyJhbGciOiJIUzUxMiJ9.test_token"
LAUNCH_URL = f"https://payment.biapay.net/web/iframe/init?Authorization={ACCESS_TOKEN}"

MOCK_SUCCESS_RESPONSE = {
    "accessToken": ACCESS_TOKEN,
    "url": "https://payment.biapay.net/web/iframe/init",
    "launchUrl": LAUNCH_URL,
}

MOCK_ERROR_RESPONSE = {
    "timestamp": 1672415287206,
    "status": 500,
    "error": "Internal Server Error",
    "message": "Merchant credential is not found.",
    "path": "/api/auth/login",
}


# ---------------------------------------------------------------------------
# BIAPay client init tests
# ---------------------------------------------------------------------------

class TestBIAPayInit:
    def test_valid_init(self):
        client = BIAPay(CLIENT_ID, CLIENT_SECRET)
        assert client.client_id == CLIENT_ID
        assert client.client_secret == CLIENT_SECRET
        assert client.base_url == "https://api.biapay.net/gateway"

    def test_custom_base_url(self):
        client = BIAPay(CLIENT_ID, CLIENT_SECRET, base_url="https://gatewaydev.biapay.net")
        assert client.base_url == "https://gatewaydev.biapay.net"

    def test_trailing_slash_stripped(self):
        client = BIAPay(CLIENT_ID, CLIENT_SECRET, base_url="https://api.biapay.net/gateway/")
        assert client.base_url == "https://api.biapay.net/gateway"

    def test_missing_client_id_raises(self):
        with pytest.raises(BIAPayValidationError):
            BIAPay("", CLIENT_SECRET)

    def test_missing_client_secret_raises(self):
        with pytest.raises(BIAPayValidationError):
            BIAPay(CLIENT_ID, "")

    def test_repr(self):
        client = BIAPay(CLIENT_ID, CLIENT_SECRET)
        assert "BIAPay" in repr(client)
        assert CLIENT_ID in repr(client)


# ---------------------------------------------------------------------------
# PaymentSession tests
# ---------------------------------------------------------------------------

class TestPaymentSession:
    def setup_method(self):
        self.session = PaymentSession.from_response(MOCK_SUCCESS_RESPONSE)

    def test_from_response(self):
        assert self.session.access_token == ACCESS_TOKEN
        assert self.session.url == "https://payment.biapay.net/web/iframe/init"
        assert self.session.launch_url == LAUNCH_URL

    def test_get_iframe_url_no_lang(self):
        url = self.session.get_iframe_url()
        assert url == LAUNCH_URL

    def test_get_iframe_url_with_lang(self):
        url = self.session.get_iframe_url(lang="fr")
        assert url.endswith("&lang=fr")

    def test_get_iframe_html_contains_src(self):
        html = self.session.get_iframe_html()
        assert "<iframe" in html
        assert "src=" in html
        assert LAUNCH_URL in html

    def test_get_iframe_html_with_lang(self):
        html = self.session.get_iframe_html(lang="fr")
        assert "&lang=fr" in html

    def test_get_iframe_html_custom_attrs(self):
        html = self.session.get_iframe_html(width="800", height="500")
        assert 'width="800"' in html
        assert 'height="500"' in html

    def test_repr(self):
        assert "PaymentSession" in repr(self.session)


# ---------------------------------------------------------------------------
# CallbackPayload tests
# ---------------------------------------------------------------------------

class TestCallbackPayload:
    def _make_params(self, **overrides):
        params = {
            "orderId": "ORDER-001",
            "status": "COMPLETED",
            "currency": "XAF",
            "amount": "5000",
            "biapayTransactionId": "BIAPAY-TXN-999",
            "transactionId": "TXN-001",
            "biapaySignature": "abc123",
        }
        params.update(overrides)
        return params

    def test_from_query_params(self):
        p = CallbackPayload.from_query_params(self._make_params())
        assert p.order_id == "ORDER-001"
        assert p.status == "COMPLETED"
        assert p.amount == "5000"
        assert p.biapay_transaction_id == "BIAPAY-TXN-999"
        assert p.transaction_id == "TXN-001"
        assert p.biapay_signature == "abc123"

    def test_is_completed_true(self):
        p = CallbackPayload.from_query_params(self._make_params(status="COMPLETED"))
        assert p.is_completed is True
        assert p.is_failed is False

    def test_is_completed_case_insensitive(self):
        p = CallbackPayload.from_query_params(self._make_params(status="completed"))
        assert p.is_completed is True

    def test_is_failed(self):
        p = CallbackPayload.from_query_params(self._make_params(status="FAILED"))
        assert p.is_failed is True
        assert p.is_completed is False

    def test_missing_signature_is_none(self):
        params = self._make_params()
        del params["biapaySignature"]
        p = CallbackPayload.from_query_params(params)
        assert p.biapay_signature is None

    def test_repr(self):
        p = CallbackPayload.from_query_params(self._make_params())
        assert "CallbackPayload" in repr(p)


# ---------------------------------------------------------------------------
# Signature verification tests
# ---------------------------------------------------------------------------

class TestSignatureVerification:
    def setup_method(self):
        self.client = BIAPay(CLIENT_ID, CLIENT_SECRET)

    def _make_signature(self, order_id, biapay_txn_id, txn_id):
        raw = f"{order_id}|{biapay_txn_id}|{txn_id}"
        return hmac.new(
            CLIENT_SECRET.encode("utf-8"),
            raw.encode("utf-8"),
            hashlib.sha256,
        ).hexdigest()

    def test_valid_signature(self):
        order_id = "ORDER-001"
        biapay_txn_id = "BIAPAY-TXN-999"
        txn_id = "TXN-001"
        sig = self._make_signature(order_id, biapay_txn_id, txn_id)

        payload = CallbackPayload(
            order_id=order_id,
            status="COMPLETED",
            currency="XAF",
            amount="5000",
            biapay_transaction_id=biapay_txn_id,
            transaction_id=txn_id,
            biapay_signature=sig,
        )
        assert self.client.verify_callback_signature(payload) is True

    def test_invalid_signature(self):
        payload = CallbackPayload(
            order_id="ORDER-001",
            status="COMPLETED",
            currency="XAF",
            amount="5000",
            biapay_transaction_id="BIAPAY-TXN-999",
            transaction_id="TXN-001",
            biapay_signature="invalid_signature",
        )
        assert self.client.verify_callback_signature(payload) is False

    def test_missing_signature_raises(self):
        payload = CallbackPayload(
            order_id="ORDER-001",
            status="COMPLETED",
            currency="XAF",
            amount="5000",
            biapay_transaction_id="BIAPAY-TXN-999",
            transaction_id="TXN-001",
            biapay_signature=None,
        )
        with pytest.raises(BIAPaySignatureError):
            self.client.verify_callback_signature(payload)

    def test_process_callback_valid(self):
        order_id = "ORDER-001"
        biapay_txn_id = "BIAPAY-TXN-999"
        txn_id = "TXN-001"
        sig = self._make_signature(order_id, biapay_txn_id, txn_id)

        params = {
            "orderId": order_id,
            "status": "COMPLETED",
            "currency": "XAF",
            "amount": "5000",
            "biapayTransactionId": biapay_txn_id,
            "transactionId": txn_id,
            "biapaySignature": sig,
        }
        payload = self.client.process_callback(params)
        assert payload.is_completed is True

    def test_process_callback_invalid_raises(self):
        params = {
            "orderId": "ORDER-001",
            "status": "COMPLETED",
            "currency": "XAF",
            "amount": "5000",
            "biapayTransactionId": "BIAPAY-TXN-999",
            "transactionId": "TXN-001",
            "biapaySignature": "wrong",
        }
        with pytest.raises(BIAPaySignatureError):
            self.client.process_callback(params)

    def test_process_callback_skip_verification(self):
        params = {
            "orderId": "ORDER-001",
            "status": "FAILED",
            "currency": "XAF",
            "amount": "5000",
            "biapayTransactionId": "BIAPAY-TXN-999",
            "transactionId": "TXN-001",
            "biapaySignature": "any_value",
        }
        # Should not raise even with wrong signature
        payload = self.client.process_callback(params, verify_signature=False)
        assert payload.is_failed is True
