# type: ignore
# This is since `behave` is really mistyped and all dynamic.
# Might be handled later.
from behave import when, then, register_type
from behave.api.async_step import async_run_until_complete
from json import loads
from typing import Any
from utils.consts import ERROR_HEADER_KEY
from utils.helpers import get_key_path
from utils.client import make_request, make_tls_pass_through_request

_PROXIFIED_STR = "through Lunar Proxy"
_NOT_PROXIFIED_STR = "directly to API Provider"


def parse_is_proxified(text: str) -> bool:
    if text == _PROXIFIED_STR:
        return True
    if text == _NOT_PROXIFIED_STR:
        return False
    raise Exception("unsupported")


def have_or_not(text: str) -> bool:
    if text == "have":
        return True
    if text == "dont have":
        return False
    raise Exception("unsupported")


def parse_int(text: str) -> int:
    return int(text)


register_type(IsProxified=parse_is_proxified)
register_type(Int=parse_int)
register_type(HaveOrNot=have_or_not)


@when(
    "Request to {scheme}:// {host} :{port:Int} {path:Path} is made through Lunar Proxy without query param based redirection"
)
@async_run_until_complete
async def step_impl(context: Any, scheme: str, host: str, port: int, path: str):
    response = await make_request(
        host=host,
        path=path,
        is_proxified=True,
        header_based_redirection=True,
        scheme=scheme,
        port=port,
    )
    context.proxified_response = response


@when(
    "Request to {scheme}:// {host} {path:Path} is made through Lunar Proxy TLS Passthrough"
)
@async_run_until_complete
async def step_impl(context: Any, scheme: str, host: str, path: str):
    print(f"making TLS Passthrough request to {scheme}://{host}{path}")
    response = await make_tls_pass_through_request(url=f"{scheme}://{host}{path}")
    context.proxified_response = response


@when(
    "Request to {scheme}:// {host} :{port:Int} {path:Path} is made through Lunar Proxy without x-lunar-host header nor query param based redirection"
)
@async_run_until_complete
async def step_impl(context: Any, scheme: str, host: str, port: int, path: str):
    response = await make_request(
        host=host,
        path=path,
        is_proxified=True,
        header_based_redirection=True,
        scheme=scheme,
        port=port,
        use_x_lunar_host=False,
        with_routing_type=False,
    )
    context.proxified_response = response


@when(
    "Request to {method} {scheme}:// {host} :{port:Int} {path:Path} is made {is_proxified:IsProxified}"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    port: int,
    path: str,
    method: str,
    is_proxified: bool,
):
    response = await make_request(
        host=host,
        path=path,
        is_proxified=is_proxified,
        scheme=scheme,
        port=port,
        method=method,
    )
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@when(
    "Request to {scheme}:// {host} :{port:Int} {path:Path} is made {is_proxified:IsProxified}"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    port: int,
    path: str,
    is_proxified: bool,
):
    response = await make_request(
        host=host, path=path, is_proxified=is_proxified, scheme=scheme, port=port
    )
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


# this step allows passing request with no port in Host header
@when("Request to {scheme}:// {host} {path:Path} is made {is_proxified:IsProxified}")
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    path: str,
    is_proxified: bool,
):
    response = await make_request(
        host=host, path=path, is_proxified=is_proxified, scheme=scheme, port=None
    )
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@when(
    "A request to {scheme}:// {host} :{port} {path:Path} is made through Lunar Proxy with query param based redirection"
)
@async_run_until_complete
async def step_impl(context: Any, scheme: str, host: str, port: int, path: str):
    response = await make_request(
        host=host,
        path=path,
        port=port,
        is_proxified=True,
        header_based_redirection=False,
        scheme=scheme,
    )
    context.proxified_response = response


@when(
    "A request to {scheme}:// {host} :{port} {path:Path} is made through Lunar Proxy with query param based redirection using host header"
)
@async_run_until_complete
async def step_impl(context: Any, scheme: str, host: str, port: int, path: str):
    response = await make_request(
        host=host,
        path=path,
        port=port,
        is_proxified=True,
        header_based_redirection=False,
        scheme=scheme,
        use_x_lunar_host=False,
    )
    context.proxified_response = response


@when(
    "A request to {scheme}:// {host} {path:Path} is made to proxy with header '{header_key}: {header_value}'"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    path: str,
    header_key: str | None,
    header_value: str | None,
):
    response = await make_request(
        host=host,
        path=path,
        is_proxified=True,
        header_key=header_key,
        header_value=header_value,
    )
    context.proxified_response = response


@when(
    "A request to {scheme}:// {host} :{port:Int} {path:Path} is made {is_proxified:IsProxified} with header '{header_key}: {header_value}'"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    path: str,
    port: int,
    is_proxified: bool,
    header_key: str | None,
    header_value: str | None,
):
    response = await make_request(
        host=host,
        path=path,
        is_proxified=is_proxified,
        header_key=header_key,
        header_value=header_value,
        scheme=scheme,
        port=port,
    )
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@when(
    "A request to {scheme}:// {host} :{port:Int} {path:Path} is made {is_proxified:IsProxified} with previously returned sequence id"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    port: int,
    path: str,
    is_proxified: bool,
):
    response = await make_request(
        host,
        path,
        is_proxified,
        port=port,
        scheme=scheme,
        header_key="x-lunar-sequence-id",
        header_value=context.proxified_response.headers.get(
            "x-lunar-sequence-id", None
        ),
    )
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@when(
    "A request to {scheme}:// {host} :{port:Int} {path:Path} is made {is_proxified:IsProxified}"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    scheme: str,
    host: str,
    port: int,
    path: str,
    is_proxified: bool,
):
    response = await make_request(host, path, is_proxified, port=port, scheme=scheme)
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@when(
    "A request is made {is_proxified:IsProxified} with header '{header_key}: {header_value}'"
)
@async_run_until_complete
async def step_impl(
    context: Any,
    is_proxified: bool,
    header_key: str | None,
    header_value: str | None,
):
    response = await make_request(
        "mox",
        "/uuid",
        is_proxified=is_proxified,
        header_key=header_key,
        header_value=header_value,
        port=8888,
    )
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@when("A request to {host} {path:Path} is made {is_proxified:IsProxified}")
@async_run_until_complete
async def step_impl(
    context: Any,
    host: str,
    path: str,
    is_proxified: bool,
):
    response = await make_request(host, path, is_proxified)
    if is_proxified:
        context.proxified_response = response
    else:
        context.direct_response = response


@then("Responses have the same body and status")
@async_run_until_complete
async def step_impl(context: Any):
    print("***")
    print(context.direct_response)
    print("***")
    print(context.proxified_response)
    print("***")
    assert context.direct_response.body == context.proxified_response.body
    assert context.direct_response.status == context.proxified_response.status


@then("Proxified response body's `{key_path}` is {expected_value}")
@async_run_until_complete
async def step_impl(context: Any, key_path: str, expected_value: str):
    print(f"proxified response: {context.proxified_response}")
    body = loads(context.proxified_response.body)
    value, found = get_key_path(body, key_path)
    assert found, f"key {key_path} not found in response body"
    assert value == expected_value


@then("Proxified response body has no key `{key_path}`")
@async_run_until_complete
async def step_impl(context: Any, key_path: str):
    print(f"proxified response: {context.proxified_response}")
    body = loads(context.proxified_response.body)
    _, found = get_key_path(body, key_path)
    assert not found, f"key {key_path} found in response body"


@then("Response {have:HaveOrNot} the error indicator header")
@async_run_until_complete
async def step_impl(context: Any, have: bool):
    print("***")
    print(context.proxified_response)
    print("***")
    if have:
        assert ERROR_HEADER_KEY in context.proxified_response.headers
    else:
        assert ERROR_HEADER_KEY not in context.proxified_response.headers


@then("Response error message should be `{errorMessage}`")
@async_run_until_complete
async def step_impl(context: Any, errorMessage: str):
    print("***")
    print(context.proxified_response)
    print("***")
    print(f"proxified response: {context.proxified_response}")
    body = context.proxified_response.body
    print(f"body: {body}")
    assert errorMessage == body
