package httpapi

import (
	"context"
	"encoding/json"
	"net/http"
	"net/url"
	"strings"

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

// listFileStorageChannels godoc
// @Summary 列出文件存储通道
// @Description 返回所有未删除的文件存储通道，用于管理上传与生成资源回传策略。
// @Tags system
// @Produce json
// @Security BearerAuth
// @Success 200 {object} FileStorageChannelListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/system/file-storage/channels [get]
func (s *Server) listFileStorageChannels(w http.ResponseWriter, r *http.Request) {
	items, err := s.store.ListFileStorageChannels(r.Context())
	if err != nil {
		s.logger.Error("list file storage channels failed", "error", err)
		writeError(w, http.StatusInternalServerError, "list file storage channels failed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"items": items})
}

// getFileStorageSettings godoc
// @Summary 获取文件存储设置
// @Description 返回文件存储系统设置；数据库对象尚未创建时返回默认设置。
// @Tags system
// @Produce json
// @Security BearerAuth
// @Success 200 {object} store.FileStorageSettings
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/system/file-storage/settings [get]
func (s *Server) getFileStorageSettings(w http.ResponseWriter, r *http.Request) {
	settings, err := s.store.GetFileStorageSettings(r.Context())
	if err != nil {
		if store.IsUndefinedDatabaseObject(err) {
			writeJSON(w, http.StatusOK, store.DefaultFileStorageSettings())
			return
		}
		s.logger.Error("get file storage settings failed", "error", err)
		writeError(w, http.StatusInternalServerError, "get file storage settings failed")
		return
	}
	writeJSON(w, http.StatusOK, settings)
}

// updateFileStorageSettings godoc
// @Summary 更新文件存储设置
// @Description 更新生成资源上传策略等文件存储系统设置。
// @Tags system
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body store.FileStorageSettingsInput true "文件存储设置"
// @Success 200 {object} store.FileStorageSettings
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/system/file-storage/settings [patch]
func (s *Server) updateFileStorageSettings(w http.ResponseWriter, r *http.Request) {
	var input store.FileStorageSettingsInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	if legacyLocalStoragePolicy(input.ResultUploadPolicy) {
		writeError(w, http.StatusBadRequest, "upload_none is no longer supported; configure an object storage channel")
		return
	}
	settings, err := s.store.UpdateFileStorageSettings(r.Context(), input)
	if err != nil {
		s.logger.Error("update file storage settings failed", "error", err)
		writeError(w, http.StatusInternalServerError, "update file storage settings failed")
		return
	}
	writeJSON(w, http.StatusOK, settings)
}

// getClientCustomizationSettings godoc
// @Summary 获取客户端自定义设置
// @Description 返回客户端名称、英文名称、平台名和图标路径；数据库对象尚未创建时返回默认设置。
// @Tags system
// @Produce json
// @Security BearerAuth
// @Success 200 {object} store.ClientCustomizationSettings
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/system/client-customization/settings [get]
func (s *Server) getClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
	s.writeClientCustomizationSettings(w, r)
}

// getPublicClientCustomizationSettings godoc
// @Summary 获取公开客户端自定义设置
// @Description 无需鉴权返回客户端名称、英文名称、平台名和图标路径；数据库对象尚未创建时返回默认设置。
// @Tags system
// @Produce json
// @Success 200 {object} store.ClientCustomizationSettings
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/public/client-customization [get]
func (s *Server) getPublicClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
	s.writeClientCustomizationSettings(w, r)
}

func (s *Server) writeClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
	settings, err := s.store.GetClientCustomizationSettings(r.Context())
	if err != nil {
		if store.IsUndefinedDatabaseObject(err) {
			writeJSON(w, http.StatusOK, store.DefaultClientCustomizationSettings())
			return
		}
		s.logger.Error("get client customization settings failed", "error", err)
		writeError(w, http.StatusInternalServerError, "get client customization settings failed")
		return
	}
	writeJSON(w, http.StatusOK, settings)
}

// updateClientCustomizationSettings godoc
// @Summary 更新客户端自定义设置
// @Description 更新客户端名称、英文名称、平台名和图标路径。
// @Tags system
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body store.ClientCustomizationSettingsInput true "客户端自定义设置"
// @Success 200 {object} store.ClientCustomizationSettings
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/system/client-customization/settings [patch]
func (s *Server) updateClientCustomizationSettings(w http.ResponseWriter, r *http.Request) {
	var input store.ClientCustomizationSettingsInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	settings, err := s.store.UpdateClientCustomizationSettings(r.Context(), input)
	if err != nil {
		s.logger.Error("update client customization settings failed", "error", err)
		writeError(w, http.StatusInternalServerError, "update client customization settings failed")
		return
	}
	writeJSON(w, http.StatusOK, settings)
}

// createFileStorageChannel godoc
// @Summary 创建文件存储通道
// @Description 创建 server-main OpenAPI、阿里云 OSS 或 S3 兼容文件存储通道。
// @Tags system
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param body body store.FileStorageChannelInput true "文件存储通道"
// @Success 201 {object} store.FileStorageChannel
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/system/file-storage/channels [post]
func (s *Server) createFileStorageChannel(w http.ResponseWriter, r *http.Request) {
	var input store.FileStorageChannelInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	if message := validateFileStorageChannelInput(input, nil); message != "" {
		writeError(w, http.StatusBadRequest, message)
		return
	}
	item, err := s.store.CreateFileStorageChannel(r.Context(), input)
	if err != nil {
		if store.IsUniqueViolation(err) {
			writeError(w, http.StatusConflict, "file storage channel key already exists")
			return
		}
		s.logger.Error("create file storage channel failed", "error", err)
		writeError(w, http.StatusInternalServerError, "create file storage channel failed")
		return
	}
	writeJSON(w, http.StatusCreated, item)
}

// updateFileStorageChannel godoc
// @Summary 更新文件存储通道
// @Description 更新指定文件存储通道的名称、凭证、场景、优先级、状态和重试策略。
// @Tags system
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param channelID path string true "文件存储通道 ID"
// @Param body body store.FileStorageChannelInput true "文件存储通道"
// @Success 200 {object} store.FileStorageChannel
// @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/system/file-storage/channels/{channelID} [patch]
func (s *Server) updateFileStorageChannel(w http.ResponseWriter, r *http.Request) {
	var input store.FileStorageChannelInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeError(w, http.StatusBadRequest, "invalid json body")
		return
	}
	existing, err := s.store.GetFileStorageChannel(r.Context(), r.PathValue("channelID"))
	if err != nil {
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "file storage channel not found")
			return
		}
		s.logger.Error("get file storage channel failed", "error", err)
		writeError(w, http.StatusInternalServerError, "get file storage channel failed")
		return
	}
	if message := validateFileStorageChannelInput(input, &existing); message != "" {
		writeError(w, http.StatusBadRequest, message)
		return
	}
	item, err := s.store.UpdateFileStorageChannel(r.Context(), r.PathValue("channelID"), input)
	if err != nil {
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "file storage channel not found")
			return
		}
		if store.IsUniqueViolation(err) {
			writeError(w, http.StatusConflict, "file storage channel key already exists")
			return
		}
		s.logger.Error("update file storage channel failed", "error", err)
		writeError(w, http.StatusInternalServerError, "update file storage channel failed")
		return
	}
	writeJSON(w, http.StatusOK, item)
}

// deleteFileStorageChannel godoc
// @Summary 删除文件存储通道
// @Description 软删除指定文件存储通道。
// @Tags system
// @Produce json
// @Security BearerAuth
// @Param channelID 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/system/file-storage/channels/{channelID} [delete]
func (s *Server) deleteFileStorageChannel(w http.ResponseWriter, r *http.Request) {
	if err := s.store.DeleteFileStorageChannel(r.Context(), r.PathValue("channelID")); err != nil {
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "file storage channel not found")
			return
		}
		s.logger.Error("delete file storage channel failed", "error", err)
		writeError(w, http.StatusInternalServerError, "delete file storage channel failed")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// testFileStorageChannel godoc
// @Summary 测试对象存储通道
// @Description 对指定 OSS 或 S3 通道检查并初始化临时文件生命周期规则，然后执行隔离的 Put、Head、Delete 探针；不返回凭据或对象键。
// @Tags system
// @Produce json
// @Security BearerAuth
// @Param channelID path string true "文件存储通道 ID"
// @Success 200 {object} FileStorageChannelTestResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 503 {object} ErrorEnvelope
// @Router /api/admin/system/file-storage/channels/{channelID}/test [post]
func (s *Server) testFileStorageChannel(w http.ResponseWriter, r *http.Request) {
	channel, err := s.store.GetFileStorageChannel(r.Context(), r.PathValue("channelID"))
	if err != nil {
		if store.IsNotFound(err) {
			writeError(w, http.StatusNotFound, "file storage channel not found")
			return
		}
		s.logger.Error("get file storage channel for test failed", "error", err)
		writeError(w, http.StatusInternalServerError, "get file storage channel failed")
		return
	}
	if channel.Provider != "aliyun_oss" && channel.Provider != "s3" {
		writeError(w, http.StatusBadRequest, "connection test is supported for aliyun_oss and s3 channels", "invalid_parameter")
		return
	}
	result, err := s.runner.TestFileStorageChannel(r.Context(), channel)
	if err != nil {
		_ = s.store.MarkFileStorageChannelFailure(context.WithoutCancel(r.Context()), channel.ID, err.Error())
		s.logger.Warn("file storage channel test failed", "channel_id", channel.ID, "provider", channel.Provider, "error", err)
		writeError(w, statusFromRunError(err), err.Error(), clients.ErrorCode(err))
		return
	}
	_ = s.store.MarkFileStorageChannelSuccess(context.WithoutCancel(r.Context()), channel.ID)
	writeJSON(w, http.StatusOK, result)
}

func validateFileStorageChannelInput(input store.FileStorageChannelInput, existing *store.FileStorageChannel) string {
	provider := strings.ToLower(strings.TrimSpace(input.Provider))
	if provider == "" {
		provider = "server_main_openapi"
	}
	status := strings.ToLower(strings.TrimSpace(input.Status))
	if status == "" {
		status = "disabled"
	}
	if strings.TrimSpace(input.ChannelKey) == "" || strings.TrimSpace(input.Name) == "" {
		return "channelKey and name are required"
	}
	if status != "enabled" && status != "disabled" {
		return "status must be enabled or disabled"
	}
	if provider != "server_main_openapi" && provider != "aliyun_oss" && provider != "s3" {
		return "provider must be server_main_openapi, aliyun_oss or s3"
	}
	if provider == "server_main_openapi" {
		hasAPIKey := false
		if input.APIKey != nil {
			hasAPIKey = strings.TrimSpace(*input.APIKey) != ""
		} else if existing != nil {
			hasAPIKey = strings.TrimSpace(existing.APIKey) != ""
		}
		if status == "enabled" && !hasAPIKey {
			return "server-main OpenAPI channel requires API key before enabling"
		}
	}
	if provider == "aliyun_oss" || provider == "s3" {
		if fileStorageConfigContainsCredential(input.Config) {
			return "object storage credentials must use the write-only credential fields, not config"
		}
		endpoint := fileStorageConfigString(input.Config, "endpoint")
		if endpoint == "" || fileStorageConfigString(input.Config, "region") == "" || fileStorageConfigString(input.Config, "bucket") == "" {
			return "object storage channel requires config.endpoint, config.region and config.bucket"
		}
		if !validFileStorageBaseURL(endpoint) {
			return "object storage config.endpoint must be an http or https URL without embedded credentials"
		}
		if publicBaseURL := firstNonEmpty(
			fileStorageConfigString(input.Config, "publicBaseUrl"),
			fileStorageConfigString(input.Config, "publicBaseURL"),
			fileStorageConfigString(input.Config, "cdnDomain"),
			fileStorageConfigString(input.Config, "publicDomain"),
		); publicBaseURL != "" && !validFileStorageBaseURL(publicBaseURL) {
			return "object storage config.publicBaseUrl must be an http or https URL without embedded credentials"
		}
		if policy := firstNonEmpty(
			fileStorageConfigString(input.Config, "temporaryFileExpirePolicy"),
			fileStorageConfigString(input.Config, "apiReturnExpirePolicy"),
		); policy != "" && !validFileStorageExpirationPolicy(policy) {
			return "object storage config.temporaryFileExpirePolicy must be never, 1d, 1m, 3m or 6m"
		}
		if value, exists := input.Config["signedUrlExpiresSeconds"]; exists {
			seconds, ok := fileStorageConfigInteger(value)
			if !ok || seconds < 60 || seconds > 7*24*60*60 {
				return "object storage config.signedUrlExpiresSeconds must be an integer between 60 and 604800"
			}
		}
		accessKeyID := input.AccessKeyID
		if accessKeyID == nil {
			accessKeyID = input.AccessKey
		}
		accessKeySecret := input.AccessKeySecret
		if accessKeySecret == nil {
			accessKeySecret = input.SecretKey
		}
		hasAccessKeyID := fileStorageCredentialPresent(accessKeyID, existing, func(item *store.FileStorageChannel) string { return item.AccessKeyID })
		hasAccessKeySecret := fileStorageCredentialPresent(accessKeySecret, existing, func(item *store.FileStorageChannel) string { return item.AccessKeySecret })
		if status == "enabled" && (!hasAccessKeyID || !hasAccessKeySecret) {
			return "object storage channel requires accessKeyId and accessKeySecret before enabling"
		}
	}
	return ""
}

func validFileStorageExpirationPolicy(value string) bool {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "never", "1d", "1m", "3m", "6m":
		return true
	default:
		return false
	}
}

func fileStorageConfigInteger(value any) (int64, bool) {
	switch typed := value.(type) {
	case int:
		return int64(typed), true
	case int32:
		return int64(typed), true
	case int64:
		return typed, true
	case float64:
		converted := int64(typed)
		return converted, float64(converted) == typed
	case float32:
		converted := int64(typed)
		return converted, float32(converted) == typed
	default:
		return 0, false
	}
}

func validFileStorageBaseURL(value string) bool {
	parsed, err := url.Parse(strings.TrimSpace(value))
	return err == nil && parsed.User == nil && parsed.Host != "" && (parsed.Scheme == "http" || parsed.Scheme == "https")
}

func fileStorageConfigContainsCredential(value any) bool {
	switch typed := value.(type) {
	case map[string]any:
		for key, item := range typed {
			normalized := strings.NewReplacer("_", "", "-", "", ".", "").Replace(strings.ToLower(strings.TrimSpace(key)))
			switch normalized {
			case "apikey", "accesskey", "accesskeyid", "accesskeysecret", "secretkey", "sessiontoken", "ststoken", "password", "credential", "credentials", "authorization":
				return true
			}
			if fileStorageConfigContainsCredential(item) {
				return true
			}
		}
	case []any:
		for _, item := range typed {
			if fileStorageConfigContainsCredential(item) {
				return true
			}
		}
	}
	return false
}

func fileStorageConfigString(config map[string]any, key string) string {
	value, _ := config[key].(string)
	return strings.TrimSpace(value)
}

func fileStorageCredentialPresent(input *string, existing *store.FileStorageChannel, current func(*store.FileStorageChannel) string) bool {
	if input != nil {
		return strings.TrimSpace(*input) != ""
	}
	return existing != nil && strings.TrimSpace(current(existing)) != ""
}

func legacyLocalStoragePolicy(value string) bool {
	normalized := strings.ReplaceAll(strings.ToLower(strings.TrimSpace(value)), "-", "_")
	switch normalized {
	case "upload_none", "none", "never", "disabled", "no_upload", "skip", "skip_all":
		return true
	default:
		return false
	}
}
