import json
from typing import Dict, Union
from urllib.parse import urlencode

import pytest
from loguru import logger
from pytest_httpserver import HTTPServer
from werkzeug.wrappers import Response

from tests.wallets.fixtures.models import Mock
from tests.wallets.helpers import (
    WalletTest,
    build_test_id,
    check_assertions,
    load_funding_source,
    wallet_fixtures_from_json,
)

# todo:
# - tests for extra fields
# - tests for paid_invoices_stream
# - test particular validations


# specify where the server should bind to
@pytest.fixture(scope="session")
def httpserver_listen_address():
    return ("127.0.0.1", 8555)


@pytest.mark.anyio
@pytest.mark.parametrize(
    "test_data",
    wallet_fixtures_from_json("tests/wallets/fixtures/json/fixtures_rest.json"),
    ids=build_test_id,
)
async def test_rest_wallet(httpserver: HTTPServer, test_data: WalletTest):
    test_id = build_test_id(test_data)
    logger.info(f"[{test_id}]: test start")
    try:
        if test_data.skip:
            logger.info(f"[{test_id}]: test skip")
            pytest.skip()

        logger.info(f"[{test_id}]: apply {len(test_data.mocks)} mocks")
        for mock in test_data.mocks:
            _apply_mock(httpserver, mock)

        logger.info(f"[{test_id}]: load funding source")
        wallet = load_funding_source(test_data.funding_source)

        logger.info(f"[{test_id}]: check assertions")
        await check_assertions(wallet, test_data)
    except Exception as exc:
        logger.info(f"[{test_id}]: test failed: {exc}")
        raise exc
    finally:
        logger.info(f"[{test_id}]: test end")


def _apply_mock(httpserver: HTTPServer, mock: Mock):
    request_data: Dict[str, Union[str, dict, list]] = {}
    request_type = getattr(mock.dict(), "request_type", None)
    # request_type = mock.request_type <--- this des not work for whatever reason!!!

    if request_type == "data":
        assert isinstance(mock.response, dict), "request data must be JSON"
        request_data["data"] = urlencode(mock.response)
    elif request_type == "json":
        request_data["json"] = mock.response

    if mock.query_params:
        request_data["query_string"] = mock.query_params

    assert mock.uri, "Missing URI for HTTP mock."
    assert mock.method, "Missing method for HTTP mock."
    req = httpserver.expect_request(
        uri=mock.uri,
        headers=mock.headers,
        method=mock.method,
        **request_data,  # type: ignore
    )

    server_response: Union[str, dict, list, Response] = mock.response
    response_type = mock.response_type
    if response_type == "response":
        assert isinstance(server_response, dict), "server response must be JSON"
        server_response = Response(**server_response)
    elif response_type == "stream":
        response_type = "response"
        server_response = Response(iter(json.dumps(server_response).splitlines()))

    respond_with = f"respond_with_{response_type}"

    getattr(req, respond_with)(server_response)
