package handlers

import (
	"log"
	"net/http"
	"regexp"
	"strings"

	"wa-assistant/backend/database"
	"wa-assistant/backend/models"
	"wa-assistant/backend/services"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

// apiConfigKeys = semua key AppSetting yang dipakai untuk konfigurasi API.
var apiConfigKeys = []string{
	"api_key", "api_model", "vision_model", "embedding_model",
	"ai_provider", "deepseek_api_key", "deepseek_model",
}

// sensitiveAPIKeys = key yang disimpan terenkripsi at-rest & disamarkan saat ditampilkan.
var sensitiveAPIKeys = map[string]bool{
	"api_key":          true,
	"deepseek_api_key": true,
}

// GetAPIConfig mengembalikan seluruh konfigurasi API (key sensitif disamarkan sebagian).
func GetAPIConfig(c *gin.Context) {
	out := map[string]string{}
	for _, k := range apiConfigKeys {
		var s models.AppSetting
		if database.DB.First(&s, "`key` = ?", k).Error == nil {
			out[k] = maskIfKey(k, decodeSetting(k, s.Value))
		} else {
			out[k] = ""
		}
	}
	c.JSON(200, out)
}

// SaveAPIConfig menyimpan konfigurasi API. Nilai kosong = tidak diubah.
func SaveAPIConfig(c *gin.Context) {
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 16*1024)
	var req map[string]string
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(400, gin.H{"error": "Format tidak valid"})
		return
	}
	if provider := strings.TrimSpace(req["ai_provider"]); provider != "" {
		if provider != "openrouter" && provider != "deepseek" {
			c.JSON(400, gin.H{"error": "Pilih OpenRouter atau DeepSeek."})
			return
		}
		key := "api_key"
		if provider == "deepseek" {
			key = "deepseek_api_key"
		}
		newKey := strings.TrimSpace(req[key])
		if (newKey == "" || strings.Contains(newKey, "*")) && !services.AIProviderConfigured(provider) {
			c.JSON(400, gin.H{"error": "Masukkan API key " + services.AIProviderLabel(provider) + " sebelum mengaktifkan penyedia ini."})
			return
		}
	}
	if model := strings.TrimSpace(req["deepseek_model"]); model != "" && !regexp.MustCompile(`^deepseek-[a-zA-Z0-9._-]{1,100}$`).MatchString(model) {
		c.JSON(400, gin.H{"error": "Pilih model dari katalog DeepSeek."})
		return
	}
	updates := map[string]string{}
	reindex := false
	for _, k := range apiConfigKeys {
		if v, ok := req[k]; ok && strings.TrimSpace(v) != "" {
			v = strings.TrimSpace(v)
			if len(v) > 2048 || strings.ContainsAny(v, "\r\n") {
				c.JSON(400, gin.H{"error": "Nilai konfigurasi tidak valid."})
				return
			}
			// Nilai tersamar yang dikirim kembali UI bukan key baru.
			if sensitiveAPIKeys[k] && strings.Contains(v, "*") {
				continue
			}
			stored := v
			if sensitiveAPIKeys[k] {
				enc, err := services.EncryptSecret(v)
				if err != nil {
					log.Printf("SaveAPIConfig gagal enkripsi %s: %v", k, err)
					c.JSON(500, gin.H{"error": "Gagal menyimpan konfigurasi"})
					return
				}
				stored = enc
			}
			updates[k] = stored
			if (k == "api_key" || k == "embedding_model") && database.GetAppSetting(k, "") != stored {
				reindex = true
			}
		}
	}
	if err := database.DB.Transaction(func(tx *gorm.DB) error {
		for key, value := range updates {
			if err := tx.Save(&models.AppSetting{Key: key, Value: value}).Error; err != nil {
				return err
			}
		}
		return nil
	}); err != nil {
		c.JSON(500, gin.H{"error": "Konfigurasi belum tersimpan. Coba lagi."})
		return
	}
	// Terapkan model/key baru saat itu juga. Perubahan model embedding otomatis
	// mengindeks ulang knowledge yang signature modelnya berbeda.
	services.InitAI()
	if reindex {
		services.InitEmbedding()
		services.Go("ReindexKnowledgeAfterAPIConfig", services.BackfillEmbeddings)
	}
	c.JSON(200, gin.H{"message": "Konfigurasi API disimpan"})
}

// ListEmbeddingModels mengembalikan katalog model embedding terbaru dari OpenRouter.
func ListEmbeddingModels(c *gin.Context) {
	models, err := services.ListOpenRouterEmbeddingModels(c.Request.Context())
	if err != nil {
		c.JSON(502, gin.H{"error": err.Error()})
		return
	}
	c.JSON(200, gin.H{"data": models})
}

// GET preserves the OpenRouter catalog used by older UI. POST previews a
// provider/key before activation, so loading models cannot switch live traffic.
func ListChatModels(c *gin.Context) {
	provider, key := c.DefaultQuery("provider", "openrouter"), ""
	if c.Request.Method == http.MethodPost {
		c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8*1024)
		var req struct {
			Provider string `json:"provider"`
			APIKey   string `json:"api_key"`
		}
		if c.ShouldBindJSON(&req) != nil || len(req.APIKey) > 2048 {
			c.JSON(400, gin.H{"error": "Konfigurasi katalog tidak valid."})
			return
		}
		provider, key = req.Provider, req.APIKey
	}
	if provider != "openrouter" && provider != "deepseek" {
		c.JSON(400, gin.H{"error": "Penyedia AI tidak valid."})
		return
	}
	models, err := services.ListProviderChatModels(c.Request.Context(), provider, key)
	if err != nil {
		c.JSON(502, gin.H{"error": err.Error()})
		return
	}
	c.JSON(200, gin.H{"data": models})
}

func ListVisionModels(c *gin.Context) {
	models, err := services.ListOpenRouterVisionModels(c.Request.Context())
	if err != nil {
		c.JSON(502, gin.H{"error": err.Error()})
		return
	}
	c.JSON(200, gin.H{"data": models})
}

// GetAPIConfigRaw = sama seperti GetAPIConfig tapi key TIDAK disamarkan & sudah didekripsi
// (dipakai internal service, bukan API publik).
func GetAPIConfigRaw() map[string]string {
	out := map[string]string{}
	for _, k := range apiConfigKeys {
		var s models.AppSetting
		if database.DB.First(&s, "`key` = ?", k).Error == nil {
			out[k] = decodeSetting(k, s.Value)
		}
	}
	return out
}

// decodeSetting mendekripsi nilai untuk key sensitif; key lain dikembalikan apa adanya.
func decodeSetting(key, value string) string {
	if !sensitiveAPIKeys[key] {
		return value
	}
	plain, err := services.DecryptSecret(value)
	if err != nil {
		log.Printf("decodeSetting gagal dekripsi %s: %v", key, err)
		return ""
	}
	return plain
}

func maskIfKey(key, value string) string {
	if value == "" {
		return ""
	}
	// Hanya sembunyikan key yang sensitif
	if sensitiveAPIKeys[key] {
		if len(value) <= 8 {
			return "***"
		}
		return value[:4] + "****" + value[len(value)-4:]
	}
	return value
}
