package api

import (
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"

	_ "oci-portal/internal/aiwire" // swagger 注解引用
	_ "oci-portal/internal/model"  // swagger 注解引用

	"oci-portal/internal/service"
)

// aiAdminHandler 处理面板侧 AI 网关管理接口(secured 组,自动进系统日志)。
type aiAdminHandler struct {
	gw *service.AiGatewayService
}

// ---- 密钥 ----

// @Summary  ---- 密钥 ----
// @Tags     AI 管理
// @Success  200 {object} itemsResponse[model.AiKey]
// @Security BearerAuth
// @Router   /api/v1/ai-keys [get]
func (h *aiAdminHandler) listKeys(c *gin.Context) {
	keys, err := h.gw.Keys(c.Request.Context())
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": keys})
}

// createKey 生成密钥;明文仅在本响应返回一次。
//
// @Summary  生成密钥
// @Tags     AI 管理
// @Param    body body object true "请求体(见接口说明)"
// @Success  201 {object} aiKeyCreateResponse "key 为明文密钥,仅本次返回"
// @Security BearerAuth
// @Router   /api/v1/ai-keys [post]
func (h *aiAdminHandler) createKey(c *gin.Context) {
	var req struct {
		Name   string   `json:"name" binding:"required"`
		Value  string   `json:"value"`
		Group  string   `json:"group"`
		Models []string `json:"models"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	plaintext, key, err := h.gw.CreateKey(c.Request.Context(), req.Name, req.Value, req.Group, req.Models)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, gin.H{"key": plaintext, "item": key})
}

// @Summary  更新 AI 网关密钥
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Param    body body object true "请求体(见接口说明)"
// @Success  204 "无内容"
// @Security BearerAuth
// @Router   /api/v1/ai-keys/{id} [put]
func (h *aiAdminHandler) updateKey(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	var req struct {
		Name    string    `json:"name"`
		Enabled *bool     `json:"enabled"`
		Group   *string   `json:"group"`
		Models  *[]string `json:"models"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := h.gw.UpdateKey(c.Request.Context(), id, req.Name, req.Enabled, req.Group, req.Models); err != nil {
		respondError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

// @Summary  删除 AI 网关密钥
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Success  204 "无内容"
// @Security BearerAuth
// @Router   /api/v1/ai-keys/{id} [delete]
func (h *aiAdminHandler) deleteKey(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	if err := h.gw.DeleteKey(c.Request.Context(), id); err != nil {
		respondError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

// updateKeyContentLog 设置密钥内容日志窗口(红线例外:显式限时开启,0 关闭)。
//
// @Summary  设置密钥内容日志窗口
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Param    body body object true "请求体(见接口说明)"
// @Success  200 {object} model.AiKey
// @Security BearerAuth
// @Router   /api/v1/ai-keys/{id}/content-log [put]
func (h *aiAdminHandler) updateKeyContentLog(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	var req struct {
		Hours int `json:"hours"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	key, err := h.gw.UpdateKeyContentLog(c.Request.Context(), id, req.Hours)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, key)
}

// listContentLogs 分页查询内容日志(可选 keyId / callLogId 过滤)。
//
// @Summary  分页查询内容日志
// @Tags     AI 管理
// @Success  200 {object} pagedResponse[model.AiCallLog]
// @Security BearerAuth
// @Router   /api/v1/ai-content-logs [get]
func (h *aiAdminHandler) listContentLogs(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
	keyID, _ := strconv.Atoi(c.DefaultQuery("keyId", "0"))
	callLogID, _ := strconv.Atoi(c.DefaultQuery("callLogId", "0"))
	items, total, err := h.gw.ContentLogs(c.Request.Context(), uint(keyID), uint(callLogID), page, size)
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

// ---- 渠道 ----

// @Summary  ---- 渠道 ----
// @Tags     AI 管理
// @Success  200 {object} itemsResponse[model.AiChannel]
// @Security BearerAuth
// @Router   /api/v1/ai-channels [get]
func (h *aiAdminHandler) listChannels(c *gin.Context) {
	chs, err := h.gw.Channels(c.Request.Context())
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": chs})
}

// @Summary  创建 AI 渠道
// @Tags     AI 管理
// @Param    body body service.ChannelInput true "请求体"
// @Success  201 {object} model.AiChannel
// @Security BearerAuth
// @Router   /api/v1/ai-channels [post]
func (h *aiAdminHandler) createChannel(c *gin.Context) {
	var req service.ChannelInput
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	ch, err := h.gw.CreateChannel(c.Request.Context(), req)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, ch)
}

// @Summary  更新 AI 渠道
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Param    body body service.ChannelInput true "请求体"
// @Success  204 "无内容"
// @Security BearerAuth
// @Router   /api/v1/ai-channels/{id} [put]
func (h *aiAdminHandler) updateChannel(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	var req service.ChannelInput
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := h.gw.UpdateChannel(c.Request.Context(), id, req); err != nil {
		respondError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

// @Summary  删除 AI 渠道
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Success  204 "无内容"
// @Security BearerAuth
// @Router   /api/v1/ai-channels/{id} [delete]
func (h *aiAdminHandler) deleteChannel(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	if err := h.gw.DeleteChannel(c.Request.Context(), id); err != nil {
		respondError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

// probeChannel 触发探测:服务可见性 + 模型同步 + 配额试调,返回更新后的渠道。
//
// @Summary  触发探测:服务可见性 + 模型同步 + 配额试调,返回更新后的渠道
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Success  200 {object} model.AiChannel
// @Security BearerAuth
// @Router   /api/v1/ai-channels/{id}/probe [post]
func (h *aiAdminHandler) probeChannel(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	ch, err := h.gw.ProbeChannel(c.Request.Context(), id)
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, ch)
}

// @Summary  同步渠道模型缓存
// @Tags     AI 管理
// @Param    id path int true "配置 ID"
// @Success  200 {object} itemsResponse[model.AiModelCache]
// @Security BearerAuth
// @Router   /api/v1/ai-channels/{id}/sync-models [post]
func (h *aiAdminHandler) syncChannelModels(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	models, err := h.gw.SyncModels(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": models})
}

// @Summary  渠道模型缓存列表
// @Tags     AI 管理
// @Param    id path int true "渠道 ID"
// @Success  200 {object} itemsResponse[model.AiModelCache]
// @Security BearerAuth
// @Router   /api/v1/ai-channels/{id}/models [get]
func (h *aiAdminHandler) listChannelModels(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	models, err := h.gw.ChannelModels(c.Request.Context(), id)
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": models})
}

type testChannelModelRequest struct {
	Model string `json:"model" binding:"required"`
}

// @Summary  测试渠道模型(max_tokens=16 试调;通过即设为探测验证模型并按需置渠道可用)
// @Tags     AI 管理
// @Param    id path int true "渠道 ID"
// @Param    body body testChannelModelRequest true "模型名"
// @Success  200 {object} model.AiChannel
// @Failure  502 {object} errorResponse "试调未通过"
// @Security BearerAuth
// @Router   /api/v1/ai-channels/{id}/test-model [post]
func (h *aiAdminHandler) testChannelModel(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	var req testChannelModelRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	ch, err := h.gw.TestChannelModel(c.Request.Context(), id, req.Model)
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, ch)
}

// ---- 聚合模型与调用日志 ----

// @Summary  ---- 聚合模型与调用日志 ----
// @Tags     AI 管理
// @Success  200 {object} itemsResponse[aiwire.Model]
// @Security BearerAuth
// @Router   /api/v1/ai-models [get]
func (h *aiAdminHandler) gatewayModels(c *gin.Context) {
	list, err := h.gw.GatewayModels(c.Request.Context(), "")
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": list.Data})
}

// ---- 模型黑名单 ----

// @Summary  模型黑名单列表
// @Tags     AI 管理
// @Success  200 {object} itemsResponse[model.AiModelBlacklist]
// @Security BearerAuth
// @Router   /api/v1/ai-blacklist [get]
func (h *aiAdminHandler) listBlacklist(c *gin.Context) {
	items, err := h.gw.Blacklist(c.Request.Context())
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items})
}

// addBlacklist 拉黑模型:删除全部渠道缓存中的同名条目,后续同步 / 探测均过滤。
//
// @Summary  添加模型黑名单
// @Tags     AI 管理
// @Param    body body object true "请求体:{name: 模型名}"
// @Success  201 {object} itemResponse[model.AiModelBlacklist]
// @Security BearerAuth
// @Router   /api/v1/ai-blacklist [post]
func (h *aiAdminHandler) addBlacklist(c *gin.Context) {
	var req struct {
		Name string `json:"name" binding:"required"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	item, err := h.gw.AddBlacklist(c.Request.Context(), req.Name)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, gin.H{"item": item})
}

// @Summary  移除模型黑名单(重新同步后模型恢复入池)
// @Tags     AI 管理
// @Param    id path int true "黑名单条目 ID"
// @Success  204 "无内容"
// @Security BearerAuth
// @Router   /api/v1/ai-blacklist/{id} [delete]
func (h *aiAdminHandler) removeBlacklist(c *gin.Context) {
	id, ok := aiPathID(c)
	if !ok {
		return
	}
	if err := h.gw.RemoveBlacklist(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.Status(http.StatusNoContent)
}

// @Summary  AI 调用日志列表
// @Tags     AI 管理
// @Success  200 {object} pagedResponse[model.AiContentLog]
// @Security BearerAuth
// @Router   /api/v1/ai-logs [get]
func (h *aiAdminHandler) listLogs(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	size, _ := strconv.Atoi(c.DefaultQuery("size", "50"))
	items, total, err := h.gw.CallLogs(c.Request.Context(), page, size)
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}

func aiPathID(c *gin.Context) (uint, bool) {
	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return 0, false
	}
	return uint(id), true
}

// modelCatalog 返回启用渠道聚合去重后的模型目录(含能力),黑名单添加弹窗用。
//
// @Summary  聚合模型目录
// @Tags     AI 管理
// @Success  200 {object} itemsResponse[service.AggregatedModel]
// @Security BearerAuth
// @Router   /api/v1/ai-model-catalog [get]
func (h *aiAdminHandler) modelCatalog(c *gin.Context) {
	items, err := h.gw.AggregatedModels(c.Request.Context())
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": items})
}

// aiSettingsResponse 是 AI 网关全局设置(文档与响应共用)。
type aiSettingsResponse struct {
	// FilterDeprecated 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除
	FilterDeprecated bool `json:"filterDeprecated"`
	// StreamGuardEnabled / StreamGuardKB 是 Responses 流式保险丝:
	// instructions+tools 合计超阈值(KB)的流式请求改非流式上游并合成 SSE
	StreamGuardEnabled bool `json:"streamGuardEnabled"`
	StreamGuardKB      int  `json:"streamGuardKB"`
	// GrokWebSearch / GrokXSearch 是 xai. 模型服务端搜索工具默认注入开关;
	// 请求 tools 已包含同名工具时不覆盖
	GrokWebSearch bool `json:"grokWebSearch"`
	GrokXSearch   bool `json:"grokXSearch"`
	// UpstreamWaitSeconds 是 responses 直通的上游无响应预算(秒):非流式为单次
	// 尝试总超时,流式为等待响应头预算;multi-agent/搜索类模型需远超 60s
	UpstreamWaitSeconds int `json:"upstreamWaitSeconds"`
}

// currentAiSettings 汇总网关运行时设置为响应体。
func (h *aiAdminHandler) currentAiSettings() aiSettingsResponse {
	guardOn, guardKB := h.gw.StreamGuard()
	web, x := h.gw.GrokSearch()
	return aiSettingsResponse{
		FilterDeprecated:    h.gw.FilterDeprecated(),
		StreamGuardEnabled:  guardOn,
		StreamGuardKB:       guardKB,
		GrokWebSearch:       web,
		GrokXSearch:         x,
		UpstreamWaitSeconds: int(h.gw.UpstreamWait() / time.Second),
	}
}

// aiSettings 返回 AI 网关全局设置。
//
// @Summary  AI 网关全局设置
// @Tags     AI 管理
// @Success  200 {object} aiSettingsResponse
// @Security BearerAuth
// @Router   /api/v1/ai-settings [get]
func (h *aiAdminHandler) aiSettings(c *gin.Context) {
	c.JSON(http.StatusOK, h.currentAiSettings())
}

// updateAiSettings 更新 AI 网关全局设置(过滤弃用/流式保险丝/grok 搜索工具默认注入)。
//
// @Summary  更新 AI 网关全局设置
// @Tags     AI 管理
// @Param    body body aiSettingsResponse true "全量提交;保险丝阈值限 1..1024 KB,上游无响应预算限 30..900 秒"
// @Success  200 {object} aiSettingsResponse
// @Failure  400 {object} map[string]string
// @Security BearerAuth
// @Router   /api/v1/ai-settings [put]
func (h *aiAdminHandler) updateAiSettings(c *gin.Context) {
	var req aiSettingsResponse
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.StreamGuardKB < 1 || req.StreamGuardKB > 1024 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "streamGuardKB 须在 1..1024"})
		return
	}
	if req.UpstreamWaitSeconds < 30 || req.UpstreamWaitSeconds > 900 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "upstreamWaitSeconds 须在 30..900"})
		return
	}
	ctx := c.Request.Context()
	if err := h.gw.SetFilterDeprecated(ctx, req.FilterDeprecated); err != nil {
		respondError(c, err)
		return
	}
	if err := h.gw.SetStreamGuard(ctx, req.StreamGuardEnabled, req.StreamGuardKB); err != nil {
		respondError(c, err)
		return
	}
	if err := h.gw.SetGrokSearch(ctx, req.GrokWebSearch, req.GrokXSearch); err != nil {
		respondError(c, err)
		return
	}
	if err := h.gw.SetUpstreamWait(ctx, req.UpstreamWaitSeconds); err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusOK, h.currentAiSettings())
}
