package setting

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/yaoapp/yao/config"
	"github.com/yaoapp/yao/llmprovider"
	"github.com/yaoapp/yao/openapi/oauth/authorized"
	oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
	"github.com/yaoapp/yao/openapi/response"
	"github.com/yaoapp/yao/setting"
)

const llmRolesNS = "llm.roles"

func llmEnsureEncKey() {
	if llmprovider.Global != nil && config.Conf.DB.AESKey != "" {
		llmprovider.Global.SetEncryptionKey(config.Conf.DB.AESKey)
	}
}

func llmOwner(info *oauthTypes.AuthorizedInfo) *llmprovider.ProviderOwner {
	if info.TeamID != "" {
		return &llmprovider.ProviderOwner{Type: "team", TeamID: info.TeamID}
	}
	return &llmprovider.ProviderOwner{Type: "user", UserID: info.UserID}
}

func llmScope(info *oauthTypes.AuthorizedInfo) setting.ScopeID {
	if info.TeamID != "" {
		return setting.ScopeID{Scope: setting.ScopeTeam, TeamID: info.TeamID}
	}
	return setting.ScopeID{Scope: setting.ScopeUser, UserID: info.UserID}
}

func llmCheckOwnership(p *llmprovider.Provider, info *oauthTypes.AuthorizedInfo) error {
	owner := llmOwner(info)
	if p.Owner.Type != owner.Type {
		return fmt.Errorf("provider not found")
	}
	if owner.Type == "team" && p.Owner.TeamID != owner.TeamID {
		return fmt.Errorf("provider not found")
	}
	if owner.Type == "user" && p.Owner.UserID != owner.UserID {
		return fmt.Errorf("provider not found")
	}
	return nil
}

func enrichProvider(p *llmprovider.Provider) map[string]interface{} {
	raw, _ := json.Marshal(p)
	var m map[string]interface{}
	json.Unmarshal(raw, &m)

	if p.PresetKey != "" {
		if preset := llmprovider.GetPreset(p.PresetKey); preset != nil {
			m["is_cloud"] = preset.IsCloud
			m["url_editable"] = preset.URLEditable
		}
	}

	delete(m, "connector_id")
	delete(m, "source")
	delete(m, "owner")

	return m
}

// llmModelsURL builds the models endpoint URL.
// Trailing slash means the user already specified the path prefix → append "models".
// No trailing slash → append "/v1/models" (standard OpenAI convention).
func llmModelsURL(apiURL string) string {
	if strings.HasSuffix(apiURL, "/") {
		return apiURL + "models"
	}
	return apiURL + "/v1/models"
}

// llmValidateKey tests connectivity by calling GET {apiURL}/models.
// providerType controls the auth header format (anthropic uses x-api-key).
func llmValidateKey(providerType, apiURL, apiKey string) error {
	url := llmModelsURL(apiURL)
	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return fmt.Errorf("failed to build request: %w", err)
	}
	if apiKey != "" {
		if providerType == "anthropic" {
			req.Header.Set("x-api-key", apiKey)
			req.Header.Set("anthropic-version", "2023-06-01")
		} else {
			req.Header.Set("Authorization", "Bearer "+apiKey)
		}
	}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("connection failed: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		return fmt.Errorf("invalid API key (HTTP %d)", resp.StatusCode)
	}
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("server returned HTTP %d", resp.StatusCode)
	}
	return nil
}

// ---------------------------------------------------------------------------
// Handlers
// ---------------------------------------------------------------------------

// handleLLMTest validates an API URL + Key without saving.
// POST /setting/llm/test
func handleLLMTest(c *gin.Context) {
	if !guardOwner(c) {
		return
	}

	var input struct {
		APIURL string `json:"api_url"`
		APIKey string `json:"api_key"`
		Type   string `json:"type"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}
	if input.APIURL == "" {
		respondError(c, http.StatusBadRequest, "api_url is required")
		return
	}

	url := llmModelsURL(input.APIURL)
	start := time.Now()
	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		respondError(c, http.StatusInternalServerError, err.Error())
		return
	}
	if input.APIKey != "" {
		if input.Type == "anthropic" {
			req.Header.Set("x-api-key", input.APIKey)
			req.Header.Set("anthropic-version", "2023-06-01")
		} else {
			req.Header.Set("Authorization", "Bearer "+input.APIKey)
		}
	}

	resp, err := client.Do(req)
	latency := time.Since(start).Milliseconds()

	if err != nil {
		response.RespondWithSuccess(c, http.StatusOK, llmprovider.ProviderTestResult{
			Success: false,
			Message: fmt.Sprintf("Connection failed: %s", err.Error()),
		})
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		response.RespondWithSuccess(c, http.StatusOK, llmprovider.ProviderTestResult{
			Success: false,
			Message: fmt.Sprintf("Server returned HTTP %d", resp.StatusCode),
		})
		return
	}

	response.RespondWithSuccess(c, http.StatusOK, llmprovider.ProviderTestResult{
		Success:   true,
		Message:   "Connection successful",
		LatencyMs: latency,
	})
}

// handleLLMGet returns the aggregated LLM configuration page data.
// GET /setting/llm
func handleLLMGet(c *gin.Context) {
	info := authorized.GetInfo(c)

	if llmprovider.Global == nil {
		respondError(c, http.StatusInternalServerError, "LLM provider registry not initialized")
		return
	}

	llmEnsureEncKey()

	owner := llmOwner(info)
	filter := &llmprovider.ProviderFilter{
		Owner:  owner,
		Source: llmprovider.ProviderSourceAll,
	}
	providers, err := llmprovider.Global.List(filter)
	if err != nil {
		providers = []llmprovider.Provider{}
	}

	enriched := make([]interface{}, 0, len(providers))
	for i := range providers {
		enriched = append(enriched, enrichProvider(&providers[i]))
	}

	var roles map[string]interface{}
	if setting.Global != nil {
		roles, _ = setting.Global.GetMerged(info.UserID, info.TeamID, llmRolesNS)
	}
	if roles == nil {
		roles = make(map[string]interface{})
	}

	presetList := llmprovider.GetPresets()
	presetIface := make([]interface{}, len(presetList))
	for i, p := range presetList {
		raw, _ := json.Marshal(p)
		var m map[string]interface{}
		json.Unmarshal(raw, &m)
		presetIface[i] = m
	}

	response.RespondWithSuccess(c, http.StatusOK, LLMPageData{
		Providers:       enriched,
		Roles:           roles,
		PresetProviders: presetIface,
	})
}

// handleLLMRoles saves the role assignment (default models).
// PUT /setting/llm/roles
func handleLLMRoles(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)
	scope := llmScope(info)

	var body map[string]interface{}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}

	if _, ok := body["default"]; !ok {
		respondError(c, http.StatusBadRequest, "\"default\" role is required")
		return
	}

	if llmprovider.Global == nil {
		respondError(c, http.StatusInternalServerError, "LLM provider registry not initialized")
		return
	}

	llmEnsureEncKey()

	for roleName, target := range body {
		targetMap, ok := target.(map[string]interface{})
		if !ok {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("invalid target for role \"%s\"", roleName))
			return
		}

		providerKey, _ := targetMap["provider"].(string)
		modelID, _ := targetMap["model"].(string)
		if providerKey == "" || modelID == "" {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("role \"%s\" requires provider and model", roleName))
			return
		}

		p, err := llmprovider.Global.Get(providerKey)
		if err != nil {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("provider \"%s\" not found", providerKey))
			return
		}
		if !p.Enabled {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("provider \"%s\" is not enabled", providerKey))
			return
		}
		if err := llmCheckOwnership(p, info); err != nil {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("provider \"%s\" not found", providerKey))
			return
		}

		modelFound := false
		for _, m := range p.Models {
			if m.ID == modelID {
				modelFound = true
				break
			}
		}
		if !modelFound {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("model \"%s\" not found in provider \"%s\"", modelID, providerKey))
			return
		}
	}

	if setting.Global == nil {
		respondError(c, http.StatusInternalServerError, "setting registry not initialized")
		return
	}

	if _, err := setting.Global.Set(scope, llmRolesNS, body); err != nil {
		respondError(c, http.StatusInternalServerError, err.Error())
		return
	}

	response.RespondWithSuccess(c, http.StatusOK, body)
}

// handleLLMProviderCreate creates a new LLM provider (preset or custom).
// POST /setting/llm/providers
func handleLLMProviderCreate(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)

	if llmprovider.Global == nil {
		respondError(c, http.StatusInternalServerError, "LLM provider registry not initialized")
		return
	}

	llmEnsureEncKey()

	var body map[string]interface{}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var provider llmprovider.Provider
	owner := llmOwner(info)
	provider.Owner = *owner
	provider.Source = llmprovider.ProviderSourceDynamic
	provider.Enabled = true

	presetKey, _ := body["preset_key"].(string)

	if presetKey != "" {
		preset := llmprovider.GetPreset(presetKey)
		if preset == nil {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("unknown preset: %s", presetKey))
			return
		}

		provider.Key = presetKey
		provider.Name = preset.Name
		provider.Type = preset.Type
		provider.APIURL = preset.APIURL
		provider.RequireKey = preset.RequireKey
		provider.PresetKey = presetKey

		if v, ok := body["api_url"].(string); ok && v != "" {
			provider.APIURL = v
		}
		if v, ok := body["api_key"].(string); ok && v != "" {
			provider.APIKey = v
		}
		if v, ok := body["name"].(string); ok && v != "" {
			provider.Name = v
		}

		modelIDs, hasModelIDs := body["model_ids"].([]interface{})
		if hasModelIDs && len(modelIDs) > 0 {
			idSet := make(map[string]bool, len(modelIDs))
			for _, id := range modelIDs {
				if s, ok := id.(string); ok {
					idSet[s] = true
				}
			}
			for _, m := range preset.DefaultModels {
				if idSet[m.ID] {
					provider.Models = append(provider.Models, m)
				}
			}
		} else {
			provider.Models = make([]llmprovider.ModelInfo, len(preset.DefaultModels))
			copy(provider.Models, preset.DefaultModels)
		}
	} else {
		provider.IsCustom = true

		key, _ := body["key"].(string)
		if key == "" {
			respondError(c, http.StatusBadRequest, "key is required for custom provider")
			return
		}
		provider.Key = key

		name, _ := body["name"].(string)
		if name == "" {
			respondError(c, http.StatusBadRequest, "name is required")
			return
		}
		provider.Name = name

		typ, _ := body["type"].(string)
		if typ == "" {
			typ = "openai"
		}
		provider.Type = typ

		provider.APIURL, _ = body["api_url"].(string)
		provider.APIKey, _ = body["api_key"].(string)

		if modelsRaw, ok := body["models"]; ok {
			raw, _ := json.Marshal(modelsRaw)
			var models []llmprovider.ModelInfo
			if err := json.Unmarshal(raw, &models); err == nil {
				provider.Models = models
			}
		}

		if v, ok := body["require_key"].(bool); ok {
			provider.RequireKey = v
		}
	}

	if provider.Models == nil {
		provider.Models = []llmprovider.ModelInfo{}
	}

	if provider.RequireKey && provider.APIKey != "" && provider.APIURL != "" {
		if err := llmValidateKey(provider.Type, provider.APIURL, provider.APIKey); err != nil {
			respondError(c, http.StatusBadRequest, fmt.Sprintf("API key validation failed: %s", err.Error()))
			return
		}
	}

	created, err := llmprovider.Global.Create(&provider)
	if err != nil {
		if strings.Contains(err.Error(), "already exists") {
			respondError(c, http.StatusConflict, err.Error())
		} else {
			respondError(c, http.StatusInternalServerError, err.Error())
		}
		return
	}

	masked, err := llmprovider.Global.GetMasked(created.Key)
	if err != nil {
		created.APIKey = ""
		response.RespondWithSuccess(c, http.StatusCreated, enrichProvider(created))
		return
	}
	response.RespondWithSuccess(c, http.StatusCreated, enrichProvider(masked))
}

// handleLLMProviderUpdate replaces a provider's configuration.
// Full replacement: api_key empty string preserves existing value.
// PUT /setting/llm/providers/:key
func handleLLMProviderUpdate(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)
	key := c.Param("key")

	if llmprovider.Global == nil {
		respondError(c, http.StatusInternalServerError, "LLM provider registry not initialized")
		return
	}

	llmEnsureEncKey()

	existing, err := llmprovider.Global.Get(key)
	if err != nil {
		respondError(c, http.StatusNotFound, fmt.Sprintf("provider \"%s\" not found", key))
		return
	}
	if err := llmCheckOwnership(existing, info); err != nil {
		respondError(c, http.StatusNotFound, err.Error())
		return
	}

	var body map[string]interface{}
	if err := c.ShouldBindJSON(&body); err != nil {
		respondError(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var provider llmprovider.Provider
	provider.Key = key
	provider.Owner = existing.Owner
	provider.Source = existing.Source
	provider.ConnectorID = existing.ConnectorID
	provider.PresetKey = existing.PresetKey
	provider.IsCustom = existing.IsCustom

	if v, ok := body["name"].(string); ok {
		provider.Name = v
	} else {
		provider.Name = existing.Name
	}
	if v, ok := body["type"].(string); ok {
		provider.Type = v
	} else {
		provider.Type = existing.Type
	}
	if v, ok := body["api_url"].(string); ok {
		provider.APIURL = v
	} else {
		provider.APIURL = existing.APIURL
	}

	if v, ok := body["api_key"].(string); ok && v != "" {
		provider.APIKey = v
	} else {
		provider.APIKey = existing.APIKey
	}

	if v, ok := body["enabled"].(bool); ok {
		provider.Enabled = v
	} else {
		provider.Enabled = existing.Enabled
	}
	if v, ok := body["require_key"].(bool); ok {
		provider.RequireKey = v
	} else {
		provider.RequireKey = existing.RequireKey
	}
	if v, ok := body["status"].(string); ok {
		provider.Status = v
	} else {
		provider.Status = existing.Status
	}

	if modelsRaw, ok := body["models"]; ok {
		raw, _ := json.Marshal(modelsRaw)
		var models []llmprovider.ModelInfo
		if err := json.Unmarshal(raw, &models); err == nil {
			provider.Models = models
		}
	} else {
		provider.Models = existing.Models
	}
	if provider.Models == nil {
		provider.Models = []llmprovider.ModelInfo{}
	}

	if _, err = llmprovider.Global.Update(key, &provider); err != nil {
		respondError(c, http.StatusInternalServerError, err.Error())
		return
	}

	masked, err := llmprovider.Global.GetMasked(key)
	if err != nil {
		provider.APIKey = ""
		response.RespondWithSuccess(c, http.StatusOK, enrichProvider(&provider))
		return
	}
	response.RespondWithSuccess(c, http.StatusOK, enrichProvider(masked))
}

// handleLLMProviderDelete removes a provider and cleans up role references.
// DELETE /setting/llm/providers/:key
func handleLLMProviderDelete(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)
	key := c.Param("key")

	if llmprovider.Global == nil {
		respondError(c, http.StatusInternalServerError, "LLM provider registry not initialized")
		return
	}

	llmEnsureEncKey()

	existing, err := llmprovider.Global.Get(key)
	if err != nil {
		respondError(c, http.StatusNotFound, fmt.Sprintf("provider \"%s\" not found", key))
		return
	}
	if err := llmCheckOwnership(existing, info); err != nil {
		respondError(c, http.StatusNotFound, err.Error())
		return
	}

	var warning string
	if setting.Global != nil {
		scope := llmScope(info)
		roles, _ := setting.Global.Get(scope, llmRolesNS)
		if roles != nil {
			cleaned := false
			for roleName, target := range roles {
				if targetMap, ok := target.(map[string]interface{}); ok {
					if provKey, _ := targetMap["provider"].(string); provKey == key {
						delete(roles, roleName)
						cleaned = true
					}
				}
			}
			if cleaned {
				setting.Global.Set(scope, llmRolesNS, roles)
				warning = fmt.Sprintf("roles referencing provider \"%s\" have been cleared", key)
			}
		}
	}

	if err := llmprovider.Global.Delete(key); err != nil {
		respondError(c, http.StatusInternalServerError, err.Error())
		return
	}

	result := map[string]interface{}{"success": true}
	if warning != "" {
		result["warning"] = warning
	}
	response.RespondWithSuccess(c, http.StatusOK, result)
}

// handleLLMProviderTest tests connectivity for a provider and writes back status.
// POST /setting/llm/providers/:key/test
func handleLLMProviderTest(c *gin.Context) {
	if !guardOwner(c) {
		return
	}
	info := authorized.GetInfo(c)
	key := c.Param("key")

	if llmprovider.Global == nil {
		respondError(c, http.StatusInternalServerError, "LLM provider registry not initialized")
		return
	}

	llmEnsureEncKey()

	p, err := llmprovider.Global.Get(key)
	if err != nil {
		respondError(c, http.StatusNotFound, fmt.Sprintf("provider \"%s\" not found", key))
		return
	}
	if err := llmCheckOwnership(p, info); err != nil {
		respondError(c, http.StatusNotFound, err.Error())
		return
	}

	start := time.Now()
	err = llmValidateKey(p.Type, p.APIURL, p.APIKey)
	latency := time.Since(start).Milliseconds()

	var testResult llmprovider.ProviderTestResult
	if err != nil {
		testResult = llmprovider.ProviderTestResult{
			Success: false,
			Message: err.Error(),
		}
		p.Status = "disconnected"
	} else {
		testResult = llmprovider.ProviderTestResult{
			Success:   true,
			Message:   "Connection successful",
			LatencyMs: latency,
		}
		p.Status = "connected"
		llmprovider.Global.Update(key, p)
	}

	response.RespondWithSuccess(c, http.StatusOK, testResult)
}
