# retoor <retoor@molodetz.nl>

from typing import Annotated

from fastapi import Depends, APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse

from devplacepy.database import (
    db,
    get_table,
    get_users_by_uids,
    resolve_by_slug,
    search_users_by_username,
)
from devplacepy.content import (
    can_manage_instance,
    can_view_instance,
    can_view_project_containers,
)
from devplacepy.models import ContainerAdminCreateForm, ContainerEditForm
from devplacepy.responses import action_result, json_error, respond
from devplacepy.schemas import (
    AdminContainerEditOut,
    AdminContainerInstanceOut,
    AdminContainersOut,
)
from devplacepy.seo import base_seo_context, site_url, website_schema
from devplacepy.services.containers import api, store
from devplacepy.services.containers.api import ContainerError
from devplacepy.services.audit import record as audit
from devplacepy.utils import not_found, require_admin
from devplacepy.dependencies import json_or_form

router = APIRouter()

BOOT_LANGUAGES = list(api.BOOT_LANGUAGES)
RESTART_POLICIES = list(store.RESTART_POLICIES)

def _project_index(project_uids: set) -> dict:
    if not project_uids or "projects" not in db.tables:
        return {}
    table = get_table("projects")
    rows = table.find(table.table.columns.uid.in_(list(project_uids)))
    return {row["uid"]: row for row in rows}

def _decorate(instances: list, viewer: dict | None = None) -> list:
    index = _project_index({inst["project_uid"] for inst in instances})
    decorated = []
    for inst in instances:
        project = index.get(inst["project_uid"], {})
        if viewer is None:
            if not project or project.get("is_private"):
                continue
            manageable = False
        else:
            if not can_view_instance(inst, project or None, viewer):
                continue
            manageable = can_manage_instance(inst, project or None, viewer)
        row = dict(inst)
        row["project_title"] = project.get("title", "")
        row["project_slug"] = project.get("slug") or project.get("uid") or ""
        row["can_manage"] = manageable
        decorated.append(row)
    decorated.sort(key=lambda r: r.get("created_at", ""), reverse=True)
    return decorated

def _instance_or_404(uid: str) -> dict:
    inst = store.get_instance(uid)
    if not inst:
        raise not_found("Instance not found")
    return inst

def _project_of(inst: dict) -> dict:
    return get_table("projects").find_one(uid=inst["project_uid"]) or {}

def _viewable_instance_or_404(uid: str, viewer: dict) -> dict:
    inst = _instance_or_404(uid)
    project = _project_of(inst)
    if not can_view_instance(inst, project or None, viewer):
        raise not_found("Instance not found")
    return inst


def _manage_denied(
    request: Request, admin: dict, inst: dict, event_key: str
) -> JSONResponse | None:
    project = _project_of(inst)
    if can_manage_instance(inst, project or None, admin):
        return None
    _audit_admin(
        request,
        admin,
        event_key,
        inst,
        f"admin {admin['username']} denied {event_key} on instance {inst.get('name')}",
        result="denied",
    )
    return json_error(403, "Only the container owner or the primary administrator can manage this instance")

def _audit_admin(request: Request, admin: dict, event_key: str, inst: dict, summary: str, metadata=None, result: str = "success") -> None:
    audit.record(
        request,
        event_key,
        user=admin,
        target_type="instance",
        target_uid=inst["uid"],
        target_label=inst.get("name"),
        metadata=metadata,
        summary=summary,
        links=[audit.instance(inst["uid"], inst.get("name"))],
        result=result,
    )

@router.get("", response_class=HTMLResponse)
async def containers_index(request: Request):
    admin = require_admin(request)
    instances = _decorate(store.all_instances(), admin)
    base = site_url(request)
    seo_ctx = base_seo_context(
        request,
        title="Containers - Admin",
        description="Manage container instances across all projects.",
        robots="noindex,nofollow",
        breadcrumbs=[
            {"name": "Home", "url": "/feed"},
            {"name": "Admin", "url": "/admin"},
            {"name": "Containers", "url": "/admin/containers"},
        ],
        schemas=[website_schema(base)],
    )

    return respond(
        request,
        "containers_admin.html",
        {
            **seo_ctx,
            "request": request,
            "user": admin,
            "instances": instances,
            "boot_languages": BOOT_LANGUAGES,
            "restart_policies": RESTART_POLICIES,
            "admin_section": "containers",
        },
        model=AdminContainersOut,
    )

@router.get("/data")
async def containers_index_json(request: Request):
    admin = require_admin(request)
    return JSONResponse({"instances": _decorate(store.all_instances(), admin)})

@router.get("/projects/search")
async def project_search(request: Request, q: str = ""):
    admin = require_admin(request)
    if not q or "projects" not in db.tables:
        return JSONResponse({"results": []})
    rows = db.query(
        "SELECT uid, slug, title, user_uid, is_private FROM projects "
        "WHERE deleted_at IS NULL AND title LIKE :q LIMIT 20",
        q=f"%{q}%",
    )
    results = [
        {"uid": r["uid"], "slug": r["slug"] or r["uid"], "title": r["title"]}
        for r in rows
        if can_view_project_containers(r, admin)
    ][:10]
    return JSONResponse({"results": results})

@router.get("/users/search")
async def user_search(request: Request, q: str = ""):
    require_admin(request)
    if not q:
        return JSONResponse({"results": []})
    return JSONResponse({"results": search_users_by_username(q)})

@router.post("/create")
async def container_create(
    request: Request, data: Annotated[ContainerAdminCreateForm, Depends(json_or_form(ContainerAdminCreateForm))]
):
    admin = require_admin(request)
    project = resolve_by_slug(get_table("projects"), data.project_slug)
    if not project or not can_view_project_containers(project, admin):
        return json_error(404, "project not found")
    try:
        inst = await api.create_instance(
            project,
            name=data.name,
            boot_command=data.boot_command,
            boot_language=data.boot_language,
            boot_script=data.boot_script,
            run_as_uid=data.run_as_uid,
            start_on_boot=data.start_on_boot,
            env=data.env,
            cpu_limit=data.cpu_limit,
            mem_limit=data.mem_limit,
            ports=data.ports,
            volumes=data.volumes,
            restart_policy=data.restart_policy,
            autostart=data.autostart,
            ingress_slug=data.ingress_slug,
            ingress_port=data.ingress_port,
            actor=("user", admin["uid"]),
        )
    except ContainerError as exc:
        return json_error(400, str(exc))
    _audit_admin(
        request,
        admin,
        "container.instance.create",
        inst,
        f"admin {admin['username']} created container instance {inst.get('name')}",
        metadata={
            "project": project.get("title"),
            "boot_language": data.boot_language,
            "run_as_uid": data.run_as_uid or None,
            "start_on_boot": data.start_on_boot,
        },
    )
    return action_result(request, "/admin/containers", data={"instance": inst})

@router.get("/{uid}/edit", response_class=HTMLResponse)
async def container_edit_page(request: Request, uid: str):
    admin = require_admin(request)
    inst = _viewable_instance_or_404(uid, admin)
    project = _project_of(inst)
    if not can_manage_instance(inst, project or None, admin):
        return RedirectResponse(url=f"/admin/containers/{uid}", status_code=302)
    run_as_user = None
    if inst.get("run_as_uid"):
        run_as_user = get_users_by_uids([inst["run_as_uid"]]).get(inst["run_as_uid"])
    base = site_url(request)
    seo_ctx = base_seo_context(
        request,
        title=f"Edit {inst['name']} - Containers",
        description=f"Edit container instance {inst['name']}.",
        robots="noindex,nofollow",
        breadcrumbs=[
            {"name": "Home", "url": "/feed"},
            {"name": "Admin", "url": "/admin"},
            {"name": "Containers", "url": "/admin/containers"},
            {"name": inst["name"], "url": f"/admin/containers/{inst['uid']}"},
            {"name": "Edit", "url": f"/admin/containers/{inst['uid']}/edit"},
        ],
        schemas=[website_schema(base)],
    )
    return respond(
        request,
        "containers_edit.html",
        {
            **seo_ctx,
            "request": request,
            "user": admin,
            "instance": inst,
            "project": project,
            "run_as_user": run_as_user,
            "boot_languages": BOOT_LANGUAGES,
            "restart_policies": RESTART_POLICIES,
            "admin_section": "containers",
        },
        model=AdminContainerEditOut,
    )

@router.post("/{uid}/edit")
async def container_edit(
    request: Request, uid: str, data: Annotated[ContainerEditForm, Depends(json_or_form(ContainerEditForm))]
):
    admin = require_admin(request)
    inst = _viewable_instance_or_404(uid, admin)
    denied = _manage_denied(request, admin, inst, "container.instance.configure")
    if denied:
        return denied
    try:
        updated = api.update_instance_config(
            inst,
            run_as_uid=data.run_as_uid,
            boot_language=data.boot_language,
            boot_script=data.boot_script,
            boot_command=data.boot_command,
            restart_policy=data.restart_policy,
            start_on_boot=data.start_on_boot,
            cpu_limit=data.cpu_limit,
            mem_limit=data.mem_limit,
            actor=("user", admin["uid"]),
        )
    except ContainerError as exc:
        return json_error(400, str(exc))
    _audit_admin(
        request,
        admin,
        "container.instance.configure",
        updated,
        f"admin {admin['username']} edited container instance {updated.get('name')}",
        metadata={
            "boot_language": data.boot_language,
            "run_as_uid": data.run_as_uid or None,
            "start_on_boot": data.start_on_boot,
            "restart_policy": data.restart_policy,
        },
    )
    return action_result(
        request, f"/admin/containers/{uid}", data={"instance": updated}
    )

_ACTIONS = {
    "start": store.DESIRED_RUNNING,
    "stop": store.DESIRED_STOPPED,
    "pause": store.DESIRED_PAUSED,
    "resume": store.DESIRED_RUNNING,
}

@router.post("/{uid}/start")
@router.post("/{uid}/stop")
@router.post("/{uid}/pause")
@router.post("/{uid}/resume")
@router.post("/{uid}/restart")
async def container_action(request: Request, uid: str):
    admin = require_admin(request)
    inst = _viewable_instance_or_404(uid, admin)
    action = request.url.path.rsplit("/", 1)[-1]
    denied = _manage_denied(request, admin, inst, f"container.instance.{action}")
    if denied:
        return denied
    actor = ("user", admin["uid"])
    if action == "restart":
        api.request_restart(inst, actor=actor)
    else:
        api.set_desired_state(inst, _ACTIONS[action], actor=actor)
    _audit_admin(
        request,
        admin,
        f"container.instance.{action}",
        inst,
        f"admin {admin['username']} {action} container instance {inst.get('name')}",
    )
    return action_result(
        request, "/admin/containers", data={"uid": uid, "action": action}
    )

@router.post("/{uid}/sync")
async def container_sync(request: Request, uid: str):
    admin = require_admin(request)
    inst = _viewable_instance_or_404(uid, admin)
    denied = _manage_denied(request, admin, inst, "container.instance.sync")
    if denied:
        return denied
    try:
        counts = await api.sync_workspace(inst, admin)
    except ContainerError as exc:
        return json_error(400, str(exc))
    _audit_admin(
        request,
        admin,
        "container.instance.sync",
        inst,
        f"admin {admin['username']} synced the workspace of instance {inst.get('name')}",
        metadata=counts,
    )
    return action_result(request, f"/admin/containers/{uid}", data=counts)

@router.post("/{uid}/delete")
async def container_delete(request: Request, uid: str):
    admin = require_admin(request)
    inst = _viewable_instance_or_404(uid, admin)
    denied = _manage_denied(request, admin, inst, "container.instance.delete")
    if denied:
        return denied
    api.mark_for_removal(inst, actor=("user", admin["uid"]))
    _audit_admin(
        request,
        admin,
        "container.instance.delete",
        inst,
        f"admin {admin['username']} removed container instance {inst.get('name')}",
    )
    return action_result(request, "/admin/containers", data={"uid": uid})

@router.get("/{uid}", response_class=HTMLResponse)
async def container_instance_page(request: Request, uid: str):
    admin = require_admin(request)
    inst = _viewable_instance_or_404(uid, admin)
    project = _project_of(inst)
    project_slug = project.get("slug") or project.get("uid") or ""
    can_manage = can_manage_instance(inst, project or None, admin)
    base = site_url(request)
    seo_ctx = base_seo_context(
        request,
        title=f"{inst['name']} - Containers",
        description=f"Container instance {inst['name']}.",
        robots="noindex,nofollow",
        breadcrumbs=[
            {"name": "Home", "url": "/feed"},
            {"name": "Admin", "url": "/admin"},
            {"name": "Containers", "url": "/admin/containers"},
            {"name": inst["name"], "url": f"/admin/containers/{inst['uid']}"},
        ],
        schemas=[website_schema(base)],
    )

    return respond(
        request,
        "containers_instance.html",
        {
            **seo_ctx,
            "request": request,
            "user": admin,
            "instance": inst,
            "project": project,
            "project_slug": project_slug,
            "events": store.list_events(inst["uid"]),
            "schedules": store.list_schedules(inst["uid"]),
            "stats": api.instance_stats(inst["uid"]),
            "runtime": await api.instance_runtime(inst),
            "can_manage": can_manage,
            "admin_section": "containers",
        },
        model=AdminContainerInstanceOut,
    )
