import base64
from collections.abc import AsyncGenerator, Generator
from multiprocessing import Process

import pytest
import pytest_asyncio
import uvicorn
from httpx import ASGITransport, AsyncClient
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route

from starlette_proxy_router import create_proxy_router
from tests.constants import PATH_DIR_CONFIG
from tests.utils.auth import create_auth_middleware


def homepage(request: Request) -> PlainTextResponse:
    user_id = request.query_params.get("user_id", "")
    return PlainTextResponse(f"Hello, {user_id}!")


def homepage_not_user_id(request: Request) -> PlainTextResponse:
    return PlainTextResponse("Hello, world!")


def create_object(request: Request) -> PlainTextResponse:
    return PlainTextResponse("Object created", status_code=201)


def get_branch_id(request: Request) -> PlainTextResponse:
    return PlainTextResponse(f"Branch: {request.path_params['branch_id']}")


def run_target_app() -> None:
    base_routes = [
        Route("/api/v1/target", homepage, methods=["GET"]),
        Route("/api/v1/target", homepage_not_user_id, methods=["PUT"]),
        Route("/api/v1/target", create_object, methods=["POST"]),
        Route("/api/v1/target", homepage, methods=["DELETE"]),
        Route("/api/v1/create-object-target", homepage, methods=["GET"]),
        Route("/", homepage, methods=["GET"]),
        Route("/additional-test", homepage, methods=["GET"]),
        Route("/additional-test/{branch_id}", get_branch_id, methods=['GET'])
    ]
    app = Starlette(routes=base_routes)
    uvicorn.run(app)


@pytest.fixture(scope="module", autouse=True)
def run_target_server() -> Generator[None]:
    proc = Process(target=run_target_app, args=(), daemon=True)
    proc.start()
    yield
    proc.kill()


@pytest.fixture(scope="module")
def proxy_app() -> Starlette:
    middleware = create_auth_middleware()
    app = Starlette(middleware=middleware)
    create_proxy_router(app, path_configs=PATH_DIR_CONFIG)
    return app


@pytest.fixture
def headers() -> dict[str, str]:
    credentials = "test_user_id:test_password"
    encoded_credentials = base64.b64encode(credentials.encode("ascii")).decode("ascii")
    return {"Authorization": f"Basic {encoded_credentials}"}


@pytest_asyncio.fixture
async def client(proxy_app: Starlette) -> AsyncGenerator[AsyncClient]:
    async with AsyncClient(
        transport=ASGITransport(app=proxy_app), base_url="http://test"
    ) as client:
        yield client


@pytest.mark.parametrize("url", ["/api/v1/proxy", "/api/v1/create-object", "/", "/additional-test"])
async def test_method_get_proxy(client: AsyncClient, url: str, headers: dict[str, str]) -> None:
    res = await client.get(url, headers=headers)
    assert res.status_code == 200
    assert res.text == "Hello, test_user_id!"


async def test_methods_proxy(client: AsyncClient, headers: dict[str, str]) -> None:
    res = await client.post("/api/v1/proxy", headers=headers)
    assert res.status_code == 201
    assert res.text == "Object created"

    res = await client.put("/api/v1/proxy", headers=headers)
    assert res.status_code == 200
    assert res.text == "Hello, world!"

    res = await client.delete("/api/v1/proxy", headers=headers)
    assert res.status_code == 200
    assert res.text == "Hello, test_user_id!"


async def test_path_params(client: AsyncClient, headers: dict[str, str]) -> None:
    res = await client.get("/additional-test/1", headers=headers)
    assert res.status_code == 200
    assert res.text == "Branch: 1"
