"""Tests for error format parsing in HttpClient._create_error and request lifecycle."""

import json

import pytest
import responses as resp_mock

from zyndpay import ZyndPay
from zyndpay.errors import (
    AuthenticationError,
    ConflictError,
    ValidationError,
    ZyndPayError,
)
from zyndpay.http_client import HttpClient

API_KEY = "zyp_test_sk_test123"
BASE_URL = "https://api.zyndpay.io/v1"


@pytest.fixture
def client():
    return ZyndPay(api_key=API_KEY)


def api_response(data, meta=None):
    body = {"success": True, "data": data}
    if meta:
        body["meta"] = meta
    return json.dumps(body)


# -- New error format: { "success": false, "error": { "code": ..., "message": ... } }


@resp_mock.activate
def test_new_format_400_uses_error_code(client):
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        json={
            "success": False,
            "error": {"code": "AMOUNT_TOO_SMALL", "message": "Min is 1"},
        },
        status=400,
    )

    with pytest.raises(ValidationError) as exc_info:
        client.payins.create(amount="0.5")

    err = exc_info.value
    assert err.code == "AMOUNT_TOO_SMALL"
    assert "Min is 1" in str(err)
    assert err.status_code == 400


@resp_mock.activate
def test_new_format_with_details(client):
    details = [
        {"field": "amount", "message": "must be at least 25"},
        {"field": "currency", "message": "unsupported"},
    ]
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        json={
            "success": False,
            "error": {
                "code": "VALIDATION_ERROR",
                "message": "Validation failed",
                "details": details,
            },
        },
        status=400,
    )

    with pytest.raises(ValidationError) as exc_info:
        client.payins.create(amount="1")

    err = exc_info.value
    assert err.details == details
    assert len(err.details) == 2
    assert err.details[0]["field"] == "amount"


@resp_mock.activate
def test_new_format_422_maps_to_validation_error(client):
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        json={
            "success": False,
            "error": {"code": "INVALID_ADDRESS", "message": "Bad address format"},
        },
        status=422,
    )

    with pytest.raises(ValidationError) as exc_info:
        client.payins.create(amount="100")

    err = exc_info.value
    assert err.code == "INVALID_ADDRESS"


# -- Legacy format: { "success": false, "message": "..." } --------------------


@resp_mock.activate
def test_legacy_format_400_defaults_code(client):
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        json={"success": False, "message": "amount must be at least 25 USDT"},
        status=400,
    )

    with pytest.raises(ValidationError) as exc_info:
        client.payins.create(amount="1")

    err = exc_info.value
    assert err.code == "VALIDATION_ERROR"
    assert "amount must be at least 25 USDT" in str(err)


@resp_mock.activate
def test_legacy_format_401_defaults_code(client):
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        json={"success": False, "message": "Invalid API key"},
        status=401,
    )

    with pytest.raises(AuthenticationError) as exc_info:
        client.payins.create(amount="100")

    err = exc_info.value
    assert err.code == "UNAUTHORIZED"


# -- Status-code-specific mappings ---------------------------------------------


@resp_mock.activate
def test_409_maps_to_conflict_error(client):
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        json={"success": False, "message": "Duplicate request"},
        status=409,
    )

    with pytest.raises(ConflictError) as exc_info:
        client.payins.create(amount="100")

    err = exc_info.value
    assert err.code == "CONFLICT"
    assert err.status_code == 409


@resp_mock.activate
def test_403_maps_to_authentication_error_with_forbidden(client):
    resp_mock.add(
        resp_mock.GET,
        f"{BASE_URL}/payin/txn_abc",
        json={"success": False, "message": "Forbidden"},
        status=403,
    )

    with pytest.raises(AuthenticationError) as exc_info:
        client.payins.get("txn_abc")

    err = exc_info.value
    assert err.code == "FORBIDDEN"


# -- X-ZyndPay-Api-Version header sent on requests ----------------------------


@resp_mock.activate
def test_api_version_header_sent(client):
    resp_mock.add(
        resp_mock.GET,
        f"{BASE_URL}/payin/txn_ver",
        body=api_response({"id": "txn_ver", "status": "CONFIRMED"}),
        content_type="application/json",
    )

    client.payins.get("txn_ver")

    req = resp_mock.calls[0].request
    assert req.headers["X-ZyndPay-Api-Version"] == "2024-01-01"


@resp_mock.activate
def test_api_version_header_sent_on_post(client):
    resp_mock.add(
        resp_mock.POST,
        f"{BASE_URL}/payin",
        body=api_response({"transactionId": "txn_hdr", "address": "Txyz"}),
        content_type="application/json",
    )

    client.payins.create(amount="50")

    req = resp_mock.calls[0].request
    assert req.headers["X-ZyndPay-Api-Version"] == "2024-01-01"


# -- _create_error unit tests (direct calls) -----------------------------------


def test_create_error_new_format_extracts_code():
    body = {
        "success": False,
        "error": {"code": "AMOUNT_TOO_SMALL", "message": "Min is 1"},
    }
    err = HttpClient._create_error(400, body, "req_123")
    assert isinstance(err, ValidationError)
    assert err.code == "AMOUNT_TOO_SMALL"
    assert err.request_id == "req_123"


def test_create_error_new_format_extracts_details():
    details = [{"field": "amount", "message": "too small"}]
    body = {
        "success": False,
        "error": {
            "code": "VALIDATION_ERROR",
            "message": "Bad",
            "details": details,
        },
    }
    err = HttpClient._create_error(400, body, None)
    assert isinstance(err, ValidationError)
    assert err.details == details


def test_create_error_legacy_format_falls_back():
    body = {"success": False, "message": "Something went wrong"}
    err = HttpClient._create_error(400, body, None)
    assert isinstance(err, ValidationError)
    assert err.code == "VALIDATION_ERROR"
    assert "Something went wrong" in str(err)


def test_create_error_unknown_status_returns_generic():
    body = {"success": False, "message": "Internal error"}
    err = HttpClient._create_error(418, body, "req_tea")
    assert isinstance(err, ZyndPayError)
    assert err.status_code == 418
    assert err.request_id == "req_tea"
