package agent

import (
	"context"
	"errors"
	"net/http"
	"slices"
	"strconv"

	"github.com/gin-gonic/gin"

	"security-center-backend/internal/model"
	"security-center-backend/internal/pkg/response"
	"security-center-backend/internal/repository"
)

// Revoke handles DELETE /agent/api-keys/:id (P2-T7).
//
// A regular user can only revoke their own keys: the repository scopes the
// update by owner, and ErrAPIKeyNotFoundOrNotOwned is reported as a plain 404
// without distinguishing "does not exist" from "belongs to someone else"
// (anti-enumeration). An admin may revoke anyone's key: the record is first
// located via the unscoped FindByID fallback, then revoked under its real
// owner.
func (h *Handler) Revoke(c *gin.Context) {
	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		response.Error(c, http.StatusBadRequest, "invalid api key id")
		return
	}
	claims := currentUser(c)
	if claims == nil {
		response.Error(c, http.StatusUnauthorized, "not authenticated")
		return
	}
	ctx := c.Request.Context()

	if slices.Contains(claims.Roles, "admin") {
		h.revokeAsAdmin(c, ctx, id)
		return
	}

	if err := h.repo.Revoke(ctx, id, claims.UserID); err != nil {
		if errors.Is(err, repository.ErrAPIKeyNotFoundOrNotOwned) {
			response.Error(c, http.StatusNotFound, "api key not found")
			return
		}
		response.Error(c, http.StatusInternalServerError, "failed to revoke api key")
		return
	}
	if rec, err := h.repo.FindByIDForOwner(ctx, id, claims.UserID); err == nil && rec != nil {
		response.Success(c, toAPIKeyResponse(rec))
		return
	}
	// 回读失败不掩盖已成功的事实：兜底只返回成功信封。
	response.Success(c, revokedFallback(id))
}

func (h *Handler) revokeAsAdmin(c *gin.Context, ctx context.Context, id uint64) {
	rec, err := h.repo.FindByID(ctx, id)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, "failed to load api key")
		return
	}
	if rec == nil {
		response.Error(c, http.StatusNotFound, "api key not found")
		return
	}
	if err := h.repo.Revoke(ctx, id, rec.OwnerUserID); err != nil {
		if errors.Is(err, repository.ErrAPIKeyNotFoundOrNotOwned) {
			response.Error(c, http.StatusNotFound, "api key not found")
			return
		}
		response.Error(c, http.StatusInternalServerError, "failed to revoke api key")
		return
	}
	if rec, err := h.repo.FindByID(ctx, id); err == nil && rec != nil {
		response.Success(c, toAPIKeyResponse(rec))
		return
	}
	response.Success(c, revokedFallback(id))
}

// revokedFallback is the minimal payload returned when the post-revoke
// read-back fails: the revoke already succeeded, so the client still gets a
// success envelope with the new status.
func revokedFallback(id uint64) gin.H {
	return gin.H{"id": id, "status": string(model.APIKeyStatusRevoked)}
}
