import asyncio
from typing import Any

import msgspec
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route

from csirtmu.external_services.netbox.netbox_client import NetboxClient
from csirtmu.external_services.outline.client import OutlineClient
from csirtmu.external_services.rt_client import RTClient
from csirtmu.self_service_backend import input_structs
from csirtmu.self_service_backend.config import AppConfig, Config
from csirtmu.self_service_backend.repository import StepRepository
from csirtmu.self_service_backend.step_builder import StepBuilder


class MSGSpecJSONResponse(JSONResponse):
    def render(self, content: Any) -> bytes:
        return msgspec.json.encode(content)


async def get_first_step(request: Request) -> MSGSpecJSONResponse:
    language = request.query_params["lang"]
    return MSGSpecJSONResponse((await request.app.state.repository.get_first_step(language)).to_dict())


async def get_next_step(request: Request) -> MSGSpecJSONResponse:
    language = request.query_params["lang"]
    return MSGSpecJSONResponse(
        [
            step.to_dict()
            for step in (await request.app.state.repository.get_next_steps(language, int(request.path_params["id"])))
        ]
    )


async def add_step(request: Request) -> MSGSpecJSONResponse:
    body = msgspec.json.decode(
        await request.body(),
        type=input_structs.TextStepInput
        | input_structs.LinkStepInput
        | input_structs.FormStepInput
        | input_structs.OutlineSearchStepInput
        | input_structs.NetboxSearchStepInput,
    )
    return MSGSpecJSONResponse(await request.app.state.repository.add_step(body.to_model()))


def get_user_tickets(request: Request) -> MSGSpecJSONResponse:
    return MSGSpecJSONResponse(request.app.state.rt_client.get_user_tickets(request.query_params["user"]))


async def submit_form(request: Request) -> MSGSpecJSONResponse:
    body = msgspec.json.decode(await request.body(), type=input_structs.FormSubmitInput)
    return MSGSpecJSONResponse(
        request.app.state.rt_client.create_ticket_from_form_data(body.data, body.requestor, body.title)
    )


def query_netbox_for_ip_addresses(request: Request) -> MSGSpecJSONResponse:
    return MSGSpecJSONResponse(request.app.state.netbox_client.search_ip_addresses(request.query_params["query"]))


def query_netbox_for_prefixes(request: Request) -> MSGSpecJSONResponse:
    return MSGSpecJSONResponse(request.app.state.netbox_client.search_prefixes(request.query_params["query"]))


def query_netbox_for_tenants(request: Request) -> MSGSpecJSONResponse:
    return MSGSpecJSONResponse(request.app.state.netbox_client.search_tenants(request.query_params["query"]))


def search_outline(request: Request) -> MSGSpecJSONResponse:
    return MSGSpecJSONResponse(request.app.state.outline_client.search_documents(request.query_params["query"]))


async def _prepare_db(config: Config) -> None:
    repo = StepRepository(config.database)
    if (await repo.get_first_step("EN")) is not None:
        return
    steps = StepBuilder().build(config.steps)
    await repo.insert_steps(steps)


def app() -> Starlette:
    c = AppConfig.get()
    if c.load_steps:
        loop = asyncio.get_event_loop()
        if loop.is_running():
            loop.create_task(_prepare_db(c))  # noqa: RUF006
        else:
            asyncio.run(_prepare_db(c))

    routes = [
        Route("/steps/first", get_first_step, methods=["GET"]),
        Route("/steps/next/{id}", get_next_step, methods=["GET"]),
        Route("/steps/add", add_step, methods=["POST"]),
        Route("/tickets", get_user_tickets, methods=["GET"]),
        Route("/submit", submit_form, methods=["POST"]),
        Route("/netbox/ipaddresses", query_netbox_for_ip_addresses, methods=["GET"]),
        Route("/netbox/prefixes", query_netbox_for_prefixes, methods=["GET"]),
        Route("/netbox/tenants", query_netbox_for_tenants, methods=["GET"]),
        Route("/outline", search_outline, methods=["GET"]),
    ]

    app_ = Starlette(routes=routes)
    app_.state.config = c
    app_.state.repository = StepRepository(c.database)
    app_.state.rt_client = RTClient(c.rt)
    app_.state.netbox_client = NetboxClient(c.netbox)
    app_.state.outline_client = OutlineClient(c.outline)
    return app_
