# retoor <retoor@molodetz.nl>

import logging
from typing import Annotated

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

from devplacepy.attachments import (
    get_attachments,
    get_orphan_attachments_batch,
    link_attachments,
    schedule_gitea_mirror,
    schedule_gitea_removal,
    soft_delete_attachment,
    split_attachment_uids,
)
from devplacepy.database import get_table
from devplacepy.dependencies import json_or_form
from devplacepy.models import IssueAttachmentForm
from devplacepy.responses import action_result, json_error
from devplacepy.schemas import IssueAttachmentsOut
from devplacepy.services.audit import record as audit
from devplacepy.services.gitea import runtime
from devplacepy.services.gitea.client import STATE_OPEN, GiteaError
from devplacepy.services.gitea.config import gitea_config
from devplacepy.utils import get_current_user, is_admin, not_found, require_user

logger = logging.getLogger(__name__)
router = APIRouter()


def _can_modify(att: dict, user: dict | None, is_open: bool) -> bool:
    if not user or not is_open:
        return False
    return att.get("user_uid") == user["uid"] or is_admin(user)


def _payload(rows: list[dict], user: dict | None, is_open: bool) -> list[dict]:
    items = []
    for row in rows:
        att = dict(row)
        att["can_modify"] = _can_modify(att, user, is_open)
        items.append(att)
    return items


async def _load_issue(number: int) -> dict:
    try:
        return await runtime.get_client().get_issue(number)
    except GiteaError as exc:
        if exc.status == 404:
            raise not_found("Issue not found")
        raise


@router.get("/{number}/attachments")
async def list_issue_attachments(request: Request, number: int):
    if not gitea_config().is_configured:
        return json_error(503, "The issue tracker is not configured")
    user = get_current_user(request)
    issue = await _load_issue(number)
    is_open = issue.get("state") == STATE_OPEN
    rows = get_attachments("issue", str(number))
    payload = {"number": number, "attachments": _payload(rows, user, is_open)}
    return JSONResponse(
        IssueAttachmentsOut.model_validate(payload).model_dump(mode="json")
    )


@router.post("/{number}/attachments")
async def add_issue_attachment(
    request: Request,
    number: int,
    data: Annotated[IssueAttachmentForm, Depends(json_or_form(IssueAttachmentForm))],
):
    user = require_user(request)
    if not gitea_config().is_configured:
        return json_error(503, "The issue tracker is not configured")
    issue = await _load_issue(number)
    if issue.get("state") != STATE_OPEN:
        audit.record(
            request,
            "issue.attachment.add",
            user=user,
            result="denied",
            target_type="issue",
            target_uid=str(number),
            summary=f"{user['username']} tried to attach to closed issue #{number}",
        )
        return json_error(409, "Attachments cannot be changed on a closed issue")
    owned = get_orphan_attachments_batch(
        split_attachment_uids(data.attachment_uids), user, admin=is_admin(user)
    )
    if not owned:
        return json_error(400, "No valid attachments to add")
    link_attachments(owned, "issue", str(number))
    for uid in owned:
        schedule_gitea_mirror(uid)
    audit.record(
        request,
        "issue.attachment.add",
        user=user,
        target_type="issue",
        target_uid=str(number),
        summary=f"{user['username']} attached {len(owned)} file(s) to issue #{number}",
        metadata={"number": number, "count": len(owned)},
        links=[audit.target("issue", str(number))],
    )
    return action_result(
        request, f"/issues/{number}", data={"count": len(owned), "uids": owned}
    )


@router.delete("/{number}/attachments/{uid}")
async def delete_issue_attachment(request: Request, number: int, uid: str):
    user = require_user(request)
    if not gitea_config().is_configured:
        return json_error(503, "The issue tracker is not configured")
    issue = await _load_issue(number)
    att = get_table("attachments").find_one(
        uid=uid, target_type="issue", target_uid=str(number), deleted_at=None
    )
    if not att:
        raise not_found("Attachment not found")
    if att.get("user_uid") and att["user_uid"] != user["uid"] and not is_admin(user):
        audit.record(
            request,
            "issue.attachment.delete",
            user=user,
            result="denied",
            target_type="issue",
            target_uid=str(number),
            summary=f"{user['username']} tried to delete another user's attachment on issue #{number}",
        )
        return json_error(403, "Not authorized")
    if issue.get("state") != STATE_OPEN:
        audit.record(
            request,
            "issue.attachment.delete",
            user=user,
            result="denied",
            target_type="issue",
            target_uid=str(number),
            summary=f"{user['username']} tried to delete an attachment on closed issue #{number}",
        )
        return json_error(409, "Attachments cannot be changed on a closed issue")
    soft_delete_attachment(uid, deleted_by=user["uid"])
    schedule_gitea_removal(att)
    audit.record(
        request,
        "issue.attachment.delete",
        user=user,
        target_type="issue",
        target_uid=str(number),
        summary=f"{user['username']} deleted an attachment on issue #{number}",
        metadata={"number": number, "uid": uid},
        links=[audit.target("issue", str(number))],
    )
    return action_result(request, f"/issues/{number}", data={"status": "deleted"})


@router.get("/{number}/comments/{cid}/attachments")
async def list_comment_attachments(request: Request, number: int, cid: int):
    if not gitea_config().is_configured:
        return json_error(503, "The issue tracker is not configured")
    user = get_current_user(request)
    issue = await _load_issue(number)
    is_open = issue.get("state") == STATE_OPEN
    rows = get_attachments("issue_comment", str(cid))
    payload = {"number": number, "attachments": _payload(rows, user, is_open)}
    return JSONResponse(
        IssueAttachmentsOut.model_validate(payload).model_dump(mode="json")
    )


@router.post("/{number}/comments/{cid}/attachments")
async def add_comment_attachment(
    request: Request,
    number: int,
    cid: int,
    data: Annotated[IssueAttachmentForm, Depends(json_or_form(IssueAttachmentForm))],
):
    user = require_user(request)
    if not gitea_config().is_configured:
        return json_error(503, "The issue tracker is not configured")
    issue = await _load_issue(number)
    if issue.get("state") != STATE_OPEN:
        return json_error(409, "Attachments cannot be changed on a closed issue")
    owned = get_orphan_attachments_batch(
        split_attachment_uids(data.attachment_uids), user, admin=is_admin(user)
    )
    if not owned:
        return json_error(400, "No valid attachments to add")
    link_attachments(owned, "issue_comment", str(cid))
    for uid in owned:
        schedule_gitea_mirror(uid)
    audit.record(
        request,
        "issue.attachment.add",
        user=user,
        target_type="issue",
        target_uid=str(number),
        summary=f"{user['username']} attached {len(owned)} file(s) to a comment on issue #{number}",
        metadata={"number": number, "comment_id": cid, "count": len(owned)},
        links=[audit.target("issue", str(number))],
    )
    return action_result(
        request, f"/issues/{number}", data={"count": len(owned), "uids": owned}
    )


@router.delete("/{number}/comments/{cid}/attachments/{uid}")
async def delete_comment_attachment(
    request: Request, number: int, cid: int, uid: str
):
    user = require_user(request)
    if not gitea_config().is_configured:
        return json_error(503, "The issue tracker is not configured")
    issue = await _load_issue(number)
    att = get_table("attachments").find_one(
        uid=uid, target_type="issue_comment", target_uid=str(cid), deleted_at=None
    )
    if not att:
        raise not_found("Attachment not found")
    if att.get("user_uid") and att["user_uid"] != user["uid"] and not is_admin(user):
        audit.record(
            request,
            "issue.attachment.delete",
            user=user,
            result="denied",
            target_type="issue",
            target_uid=str(number),
            summary=f"{user['username']} tried to delete another user's comment attachment on issue #{number}",
        )
        return json_error(403, "Not authorized")
    if issue.get("state") != STATE_OPEN:
        return json_error(409, "Attachments cannot be changed on a closed issue")
    soft_delete_attachment(uid, deleted_by=user["uid"])
    schedule_gitea_removal(att)
    audit.record(
        request,
        "issue.attachment.delete",
        user=user,
        target_type="issue",
        target_uid=str(number),
        summary=f"{user['username']} deleted a comment attachment on issue #{number}",
        metadata={"number": number, "comment_id": cid, "uid": uid},
        links=[audit.target("issue", str(number))],
    )
    return action_result(request, f"/issues/{number}", data={"status": "deleted"})
