package httpapi

import (
	"encoding/json"
	"errors"
	"net/http"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

// listAccessRules godoc
// @Summary 列出访问规则
// @Description 管理端返回用户组、租户、用户或 API Key 到平台、平台模型、基础模型的分层访问规则。主体当前层无 allow 时继承上级，存在 allow 时仅允许白名单，deny 始终优先。
// @Tags access-rules
// @Produce json
// @Security BearerAuth
// @Success 200 {object} AccessRuleListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules [get]
func (s *Server) listAccessRules(w http.ResponseWriter, r *http.Request) {
	items, err := s.store.ListAccessRules(r.Context())
	if err != nil {
		s.logger.Error("list access rules failed", "error", err)
		writeError(w, http.StatusInternalServerError, "list access rules failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": items})
}

// listAPIKeyAccessRules godoc
// @Summary 列出 API Key 访问规则
// @Description 返回当前本地用户拥有的 API Key 访问规则；不会混入其他用户或其他 API Key 的规则。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Success 200 {object} AccessRuleListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 503 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/access-rules [get]
func (s *Server) listAPIKeyAccessRules(w http.ResponseWriter, r *http.Request) {
	user, _ := auth.UserFromContext(r.Context())
	items, err := s.store.ListAPIKeyAccessRules(r.Context(), user)
	if err != nil {
		if errors.Is(err, store.ErrLocalUserRequired) {
			writeLocalUserRequired(w)
			return
		}
		s.logger.Error("list api key access rules failed", "error", err)
		writeError(w, http.StatusInternalServerError, "list api key access rules failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": items})
}

// listAPIKeyAssignableModels godoc
// @Summary 列出 API Key 可分配模型
// @Description 按当前用户自身的租户、用户组和用户分层白名单返回可分配给 API Key 的启用模型，不应用任何 API Key 层规则。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Deprecated
// @Success 200 {object} PlatformModelListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 503 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/assignable-models [get]
func (s *Server) listAPIKeyAssignableModels(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Deprecation", "true")
	user, _ := auth.UserFromContext(r.Context())
	models, err := s.store.ListAPIKeyAssignablePlatformModels(r.Context(), user)
	if err != nil {
		if errors.Is(err, store.ErrLocalUserRequired) {
			writeLocalUserRequired(w)
			return
		}
		s.logger.Error("list api key assignable models failed", "error", err)
		writeError(w, http.StatusInternalServerError, "list api key assignable models failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": s.platformModelResponses(r.Context(), models)})
}

// listAPIKeyAssignableModelsForKey godoc
// @Summary 列出指定 API Key 可分配模型
// @Description 返回全局启用、命中指定 API Key 的租户/用户组/用户基线且符合 Key scope 的可分配平台来源；当前 Key 的 allow/deny 不缩减候选，仅作为已有规则有效性诊断返回。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Param apiKeyID path string true "API Key ID"
// @Success 200 {object} APIKeyAssignableModelsResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 503 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/{apiKeyID}/assignable-models [get]
func (s *Server) listAPIKeyAssignableModelsForKey(w http.ResponseWriter, r *http.Request) {
	user, _ := auth.UserFromContext(r.Context())
	models, diagnostics, err := s.store.ListAPIKeyAssignablePlatformModelsForKey(r.Context(), user, r.PathValue("apiKeyID"))
	if err != nil {
		if errors.Is(err, store.ErrLocalUserRequired) {
			writeLocalUserRequired(w)
			return
		}
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "api key not found")
			return
		}
		s.logger.Error("list api key assignable models failed", "error", err)
		writeError(w, http.StatusInternalServerError, "list api key assignable models failed")
		return
	}
	writeJSON(w, http.StatusOK, APIKeyAssignableModelsResponse{
		Items:           s.platformModelResponses(r.Context(), models),
		RuleDiagnostics: diagnostics,
	})
}

// createAccessRule godoc
// @Summary 创建访问规则
// @Description 管理端创建一条访问控制规则；同一主体层存在任意有效 allow 后该层启用白名单，deny 始终优先。
// @Tags access-rules
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.AccessRuleInput true "访问规则请求"
// @Success 201 {object} store.AccessRule
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules [post]
func (s *Server) createAccessRule(w http.ResponseWriter, r *http.Request) {
	var input store.AccessRuleInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	if !validAccessRuleInput(input) {
		writeError(w, http.StatusBadRequest, "subject, resource and effect are required")
		return
	}
	item, err := s.store.CreateAccessRule(r.Context(), input)
	if err != nil {
		if store.IsUniqueViolation(err) {
			writeError(w, http.StatusConflict, "access rule already exists")
			return
		}
		s.logger.Error("create access rule failed", "error", err)
		writeError(w, http.StatusInternalServerError, "create access rule failed")
		return
	}
	writeJSON(w, http.StatusCreated, item)
}

// batchAccessRules godoc
// @Summary 批量写入访问规则
// @Description 管理端为同一主体批量新增、更新或删除资源访问规则。清空该主体全部 allow 会恢复上级继承。
// @Tags access-rules
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.AccessRuleBatchInput true "访问规则批量请求"
// @Success 200 {object} AccessRuleListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules/batch [post]
func (s *Server) batchAccessRules(w http.ResponseWriter, r *http.Request) {
	var input store.AccessRuleBatchInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	if !validAccessRuleBatchInput(input) {
		writeError(w, http.StatusBadRequest, "subject, effect and resources are required")
		return
	}
	items, err := s.store.BatchAccessRules(r.Context(), input)
	if err != nil {
		s.logger.Error("batch access rules failed", "error", err)
		writeError(w, http.StatusInternalServerError, "batch access rules failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": items})
}

// batchAPIKeyAccessRules godoc
// @Summary 批量写入 API Key 访问规则
// @Description 当前本地用户为自己的 API Key 批量新增、更新或删除白名单/拒绝资源；Key 无 allow 时继承父级范围，存在 allow 后仅允许命中项。
// @Tags api-keys
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.AccessRuleBatchInput true "API Key 访问规则批量请求，subjectType 必须为 api_key"
// @Success 200 {object} AccessRuleListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/access-rules/batch [post]
func (s *Server) batchAPIKeyAccessRules(w http.ResponseWriter, r *http.Request) {
	user, _ := auth.UserFromContext(r.Context())
	var input store.AccessRuleBatchInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	if !validAccessRuleBatchInput(input) || input.SubjectType != "api_key" {
		writeError(w, http.StatusBadRequest, "api key subject, effect and resources are required")
		return
	}
	items, err := s.store.BatchAPIKeyAccessRules(r.Context(), input, user)
	if err != nil {
		if errors.Is(err, store.ErrLocalUserRequired) {
			writeLocalUserRequired(w)
			return
		}
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "api key not found")
			return
		}
		if errors.Is(err, store.ErrAccessRuleResourceDenied) {
			writeError(w, http.StatusForbidden, "resource is not available for current user group or API key scope")
			return
		}
		s.logger.Error("batch api key access rules failed", "error", err)
		writeError(w, http.StatusInternalServerError, "batch api key access rules failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": items})
}

// updateAccessRule godoc
// @Summary 更新访问规则
// @Description 管理端更新一条访问控制规则。
// @Tags access-rules
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param ruleID path string true "访问规则 ID"
// @Param input body store.AccessRuleInput true "访问规则请求"
// @Success 200 {object} store.AccessRule
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules/{ruleID} [patch]
func (s *Server) updateAccessRule(w http.ResponseWriter, r *http.Request) {
	var input store.AccessRuleInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	if !validAccessRuleInput(input) {
		writeError(w, http.StatusBadRequest, "subject, resource and effect are required")
		return
	}
	item, err := s.store.UpdateAccessRule(r.Context(), r.PathValue("ruleID"), input)
	if err != nil {
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "access rule not found")
			return
		}
		if store.IsUniqueViolation(err) {
			writeError(w, http.StatusConflict, "access rule already exists")
			return
		}
		s.logger.Error("update access rule failed", "error", err)
		writeError(w, http.StatusInternalServerError, "update access rule failed")
		return
	}
	writeJSON(w, http.StatusOK, item)
}

// deleteAccessRule godoc
// @Summary 删除访问规则
// @Description 管理端删除一条访问控制规则。
// @Tags access-rules
// @Produce json
// @Security BearerAuth
// @Param ruleID path string true "访问规则 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules/{ruleID} [delete]
func (s *Server) deleteAccessRule(w http.ResponseWriter, r *http.Request) {
	if err := s.store.DeleteAccessRule(r.Context(), r.PathValue("ruleID")); err != nil {
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "access rule not found")
			return
		}
		s.logger.Error("delete access rule failed", "error", err)
		writeError(w, http.StatusInternalServerError, "delete access rule failed")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func validAccessRuleInput(input store.AccessRuleInput) bool {
	return validOneOf(input.SubjectType, "user_group", "tenant", "user", "api_key") &&
		strings.TrimSpace(input.SubjectID) != "" &&
		validOneOf(input.ResourceType, "platform", "platform_model", "base_model") &&
		strings.TrimSpace(input.ResourceID) != "" &&
		validOneOf(input.Effect, "allow", "deny") &&
		(input.Status == "" || validOneOf(input.Status, "active", "disabled"))
}

func validAccessRuleBatchInput(input store.AccessRuleBatchInput) bool {
	if !validOneOf(input.SubjectType, "user_group", "tenant", "user", "api_key") ||
		strings.TrimSpace(input.SubjectID) == "" ||
		!validOneOf(input.Effect, "allow", "deny") {
		return false
	}
	if len(input.UpsertResources) == 0 && len(input.DeleteResources) == 0 {
		return false
	}
	for _, resource := range append(input.UpsertResources, input.DeleteResources...) {
		if !validOneOf(resource.ResourceType, "platform", "platform_model", "base_model") ||
			strings.TrimSpace(resource.ResourceID) == "" ||
			(resource.Status != "" && !validOneOf(resource.Status, "active", "disabled")) {
			return false
		}
	}
	return true
}

func validOneOf(value string, allowed ...string) bool {
	value = strings.TrimSpace(value)
	for _, item := range allowed {
		if value == item {
			return true
		}
	}
	return false
}
