"""Routes for the workshop overlay type (add/remove items, manual rebuild,
admin global refresh)."""
from __future__ import annotations

import requests
from flask import Blueprint, Response, redirect, request
from sqlalchemy import delete as sa_delete
from sqlalchemy import select

from l4d2web.auth import current_user, require_admin, require_login
from l4d2web.db import session_scope
from l4d2web.models import (
    Job,
    Overlay,
    OverlayWorkshopItem,
    WorkshopItem,
)
from l4d2web.services import steam_workshop
from l4d2web.services.job_worker import enqueue_build_overlay


bp = Blueprint("workshop", __name__)


def _check_workshop_overlay_access(overlay_id: int, user, db):
    overlay = db.scalar(select(Overlay).where(Overlay.id == overlay_id))
    if overlay is None:
        return None, Response(status=404)
    if overlay.type != "workshop":
        return None, Response("not a workshop overlay", status=400)
    if overlay.user_id != user.id and not user.admin:
        return None, Response(status=403)
    return overlay, None


@bp.post("/overlays/<int:overlay_id>/items")
@require_login
def add_items(overlay_id: int) -> Response:
    user = current_user()
    assert user is not None

    raw_input = request.form.get("input", "").strip()
    mode = request.form.get("input_mode", "items")
    if not raw_input:
        return Response("missing input", status=400)

    try:
        ids = steam_workshop.parse_workshop_input(raw_input)
    except ValueError as exc:
        return Response(str(exc), status=400)

    if mode == "collection":
        if len(ids) != 1:
            return Response("collection mode expects exactly one id or url", status=400)
        try:
            ids = steam_workshop.resolve_collection(ids[0])
        except Exception as exc:
            return Response(f"failed to resolve collection: {exc}", status=502)
        if not ids:
            return Response("collection has no items", status=400)

    try:
        metas = steam_workshop.fetch_metadata_batch(ids, mode="add")
    except steam_workshop.WorkshopValidationError as exc:
        return Response(str(exc), status=400)
    except Exception as exc:
        return Response(f"steam api error: {exc}", status=502)

    with session_scope() as db:
        overlay, err = _check_workshop_overlay_access(overlay_id, user, db)
        if err is not None:
            return err
        for meta in metas:
            wi = db.scalar(select(WorkshopItem).where(WorkshopItem.steam_id == meta.steam_id))
            if wi is None:
                wi = WorkshopItem(steam_id=meta.steam_id)
                db.add(wi)
            wi.title = meta.title
            wi.filename = meta.filename
            wi.file_url = meta.file_url
            wi.file_size = meta.file_size
            wi.time_updated = meta.time_updated
            wi.preview_url = meta.preview_url
            wi.last_error = "" if meta.result == 1 else f"steam result {meta.result}"
            db.flush()

            existing = db.scalar(
                select(OverlayWorkshopItem).where(
                    OverlayWorkshopItem.overlay_id == overlay_id,
                    OverlayWorkshopItem.workshop_item_id == wi.id,
                )
            )
            if existing is None:
                db.add(OverlayWorkshopItem(overlay_id=overlay_id, workshop_item_id=wi.id))

        job = enqueue_build_overlay(db, overlay_id=overlay_id, user_id=user.id)
        job_id = job.id

    return redirect(f"/jobs/{job_id}")


@bp.post("/overlays/<int:overlay_id>/items/<int:item_id>/delete")
@require_login
def remove_item(overlay_id: int, item_id: int) -> Response:
    user = current_user()
    assert user is not None
    with session_scope() as db:
        overlay, err = _check_workshop_overlay_access(overlay_id, user, db)
        if err is not None:
            return err
        result = db.execute(
            sa_delete(OverlayWorkshopItem).where(
                OverlayWorkshopItem.overlay_id == overlay_id,
                OverlayWorkshopItem.workshop_item_id == item_id,
            )
        )
        if result.rowcount == 0:
            return Response(status=404)
        job = enqueue_build_overlay(db, overlay_id=overlay_id, user_id=user.id)
        job_id = job.id
    return redirect(f"/jobs/{job_id}")


@bp.post("/overlays/<int:overlay_id>/refresh")
@require_login
def refresh_overlay(overlay_id: int) -> Response:
    user = current_user()
    assert user is not None
    with session_scope() as db:
        overlay, err = _check_workshop_overlay_access(overlay_id, user, db)
        if err is not None:
            return err
        steam_ids = list(
            db.scalars(
                select(WorkshopItem.steam_id)
                .join(
                    OverlayWorkshopItem,
                    OverlayWorkshopItem.workshop_item_id == WorkshopItem.id,
                )
                .where(OverlayWorkshopItem.overlay_id == overlay_id)
            ).all()
        )

    if not steam_ids:
        return Response("overlay has no items", status=400)

    try:
        metas = steam_workshop.fetch_metadata_batch(steam_ids, mode="refresh")
    except requests.RequestException as exc:
        return Response(f"steam api error: {exc}", status=502)

    metas_by_id = {m.steam_id: m for m in metas}
    with session_scope() as db:
        overlay, err = _check_workshop_overlay_access(overlay_id, user, db)
        if err is not None:
            return err
        for steam_id in steam_ids:
            wi = db.scalar(select(WorkshopItem).where(WorkshopItem.steam_id == steam_id))
            if wi is None:
                continue
            meta = metas_by_id.get(steam_id)
            if meta is None:
                wi.last_error = "steam returned no entry for this item"
                continue
            wi.title = meta.title
            wi.filename = meta.filename
            wi.file_url = meta.file_url
            wi.file_size = meta.file_size
            wi.time_updated = meta.time_updated
            wi.preview_url = meta.preview_url
            wi.last_error = "" if meta.result == 1 else f"steam result {meta.result}"
        job = enqueue_build_overlay(db, overlay_id=overlay_id, user_id=user.id)
        job_id = job.id

    return redirect(f"/jobs/{job_id}")


@bp.post("/admin/workshop/refresh")
@require_admin
def admin_refresh() -> Response:
    user = current_user()
    assert user is not None
    with session_scope() as db:
        db.add(
            Job(
                user_id=user.id,
                server_id=None,
                operation="refresh_workshop_items",
                state="queued",
            )
        )
    return redirect("/admin/jobs")
