from os import path
from typing import Annotated

from docker import errors, from_env
from docker.models.containers import Container
from dotenv import load_dotenv
from fastapi import Depends, FastAPI, HTTPException
from fastapi.responses import PlainTextResponse
from fastapi.security import HTTPBasicCredentials
from starlette import status
from uvicorn import run

from .security import AuthStaticFiles, check_auth, security
from .types import ContainerRequest, SerializedContainer
from .utils import serialize_container

load_dotenv()
client = from_env()
app = FastAPI(dependencies=[Depends(check_auth)])


def select_container(
    container_name: str, credentials: Annotated[HTTPBasicCredentials, Depends(security)]
) -> Container:
    try:
        container = client.containers.get(container_name)
    except errors.APIError:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

    if (
        credentials.username != "admin"
        and container.labels.get("engine") != "pilotwings"
        and container.labels.get("owner") != credentials.username
    ):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)

    return container


@app.get("/api/containers")
def get_containers(
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> list[SerializedContainer]:
    if credentials.username == "admin":
        return [
            serialize_container(container)
            for container in client.containers.list(
                all=True, filters={"label": ["engine=pilotwings"]}
            )
        ]

    return [
        serialize_container(container)
        for container in client.containers.list(
            all=True,
            filters={"label": ["engine=pilotwings", f"owner={credentials.username}"]},
        )
    ]


@app.get("/api/container/{container_name}")
def get_container(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> SerializedContainer:
    return serialize_container(select_container(container_name, credentials))


@app.post("/api/container/{container_name}")
def create_or_update_container(
    container_name: str,
    request_body: ContainerRequest,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> SerializedContainer:
    owner = None
    networks = client.networks.list(names=["pilotwings"])

    if not networks:
        client.networks.create("pilotwings")

    client.images.pull(request_body.image)

    try:
        container = select_container(container_name, credentials)
        owner = container.labels.get("owner")
        delete_container(container_name, credentials)
    except HTTPException:
        pass

    container = client.containers.run(
        request_body.image,
        detach=True,
        environment=request_body.environment,
        labels={"engine": "pilotwings", "owner": owner or credentials.username},
        name=container_name,
        network="pilotwings",
        restart_policy={"Name": "on-failure"},
    )

    client.images.prune({"dangling": True})

    return serialize_container(container)


@app.post("/api/container/{container_name}/pull")
def pull_container(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> SerializedContainer:
    container = select_container(container_name, credentials)

    if not container.image:
        raise HTTPException(status_code=status.HTTP_410_GONE)

    request_body = ContainerRequest(
        image=container.image.tags[0], environment=container.attrs["Config"]["Env"]
    )

    return create_or_update_container(container_name, request_body, credentials)


@app.post("/api/container/{container_name}/restart")
def restart_container(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> SerializedContainer:
    container = select_container(container_name, credentials)
    container.restart()
    container.reload()
    return serialize_container(container)


@app.post("/api/container/{container_name}/start")
def start_container(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> SerializedContainer:
    container = select_container(container_name, credentials)
    container.start()
    container.reload()
    return serialize_container(container)


@app.post("/api/container/{container_name}/stop")
def stop_container(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> SerializedContainer:
    container = select_container(container_name, credentials)
    container.stop()
    container.reload()
    return serialize_container(container)


@app.get("/api/container/{container_name}/logs", response_class=PlainTextResponse)
def get_container_logs(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> str:
    container = select_container(container_name, credentials)
    return container.logs().decode()


@app.delete("/api/container/{container_name}")
def delete_container(
    container_name: str,
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> None:
    container = select_container(container_name, credentials)
    container.stop()
    container.remove(v=True, force=True)
    client.images.prune({"dangling": True})


app.mount(
    "/",
    AuthStaticFiles(
        directory=f"{path.dirname(path.realpath(__file__))}/dist", html=True
    ),
    name="static",
)


def launch() -> None:
    run(app, host="0.0.0.0")