package rag

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	FikoYTCtx "server/sistem/fikoyt_ctx"
	fiko_mongo "server/sistem/fikoyt_mongo"

	"github.com/gofiber/fiber/v3"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo/options"
)

const AyarlarVT = "FikoYT.sistem.rag.ayarlar"

// RagAyar — admin panelden yönetilen merkezi RAG/AI ayarları.
type RagAyar struct {
	ChatModel         string `json:"chatModel" bson:"chatModel"`
	EmbedModel        string `json:"embedModel" bson:"embedModel"`
	NumCtx            int    `json:"numCtx" bson:"numCtx"`
	MaxToolIterasyon  int    `json:"maxToolIterasyon" bson:"maxToolIterasyon"`
	MaxChatPerUser    int    `json:"maxChatPerUser" bson:"maxChatPerUser"`
	RagLimit          int    `json:"ragLimit" bson:"ragLimit"`
	GecmisMesajSayisi int    `json:"gecmisMesajSayisi" bson:"gecmisMesajSayisi"`
	SistemPromptEk    string `json:"sistemPromptEk" bson:"sistemPromptEk"`
	BilesenlerAktif   bool   `json:"bilesenlerAktif" bson:"bilesenlerAktif"`
	DusunmeAktif      bool   `json:"dusunmeAktif" bson:"dusunmeAktif"`
	RagAktif          bool   `json:"ragAktif" bson:"ragAktif"`
	CanliEmbedAktif   bool   `json:"canliEmbedAktif" bson:"canliEmbedAktif"`
	HerkeseAcikVtler  string `json:"herkeseAcikVtler" bson:"herkeseAcikVtler"`
	McpAnahtar        string `json:"mcpAnahtar" bson:"mcpAnahtar"`
}

// mcpErisimVarMi — MCP uçları için: admin oturumu VEYA geçerli Bearer anahtar.
// Anahtar ayarlanmamışsa dış erişim tamamen kapalıdır.
func mcpErisimVarMi(c fiber.Ctx) bool {
	if ayarAdminKontrol(c) != nil {
		return true
	}
	anahtar := ragAyar().McpAnahtar
	if anahtar == "" {
		return false
	}
	sunulan := strings.TrimPrefix(c.Get("Authorization"), "Bearer ")
	return sunulan == anahtar || c.Query("anahtar") == anahtar
}

func varsayilanAyar() RagAyar {
	return RagAyar{
		ChatModel:         ChatModel,
		EmbedModel:        EmbedModel,
		NumCtx:            8192,
		MaxToolIterasyon:  MaxToolIterasyon,
		MaxChatPerUser:    2,
		RagLimit:          10,
		GecmisMesajSayisi: 10,
		SistemPromptEk:    "",
		BilesenlerAktif:   true,
		DusunmeAktif:      true,
		RagAktif:          true,
		CanliEmbedAktif:   true,
		HerkeseAcikVtler:  "FikoYT.sistem.paylasim,FikoYT.sistem.dokumantasyon,FikoYT.sistem.soyAgaciKisiler,FikoYT.sistem.aileBaglari",
	}
}

// herkeseAcikVtListesi — Süper Yönetici olmayan kullanıcıların RAG aramasında
// erişebildiği vt önekleri. Boşsa varsayılan güvenli liste kullanılır.
func herkeseAcikVtListesi() []string {
	kaynak := strings.TrimSpace(ragAyar().HerkeseAcikVtler)
	if kaynak == "" {
		kaynak = varsayilanAyar().HerkeseAcikVtler
	}
	var vtler []string
	for _, p := range strings.Split(kaynak, ",") {
		if p = strings.TrimSpace(p); p != "" {
			vtler = append(vtler, p)
		}
	}
	return vtler
}

var (
	ayarCache      RagAyar
	ayarCacheZaman time.Time
	ayarMu         sync.Mutex
)

// ragAyar — aktif ayarları döner (60s cache, DB'de yoksa varsayılan).
func ragAyar() RagAyar {
	ayarMu.Lock()
	defer ayarMu.Unlock()
	if time.Since(ayarCacheZaman) < 60*time.Second && ayarCacheZaman != (time.Time{}) {
		return ayarCache
	}

	ayar := varsayilanAyar()
	kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")
	var doc bson.M
	err := kol.FindOne(context.TODO(), bson.M{"vt": AyarlarVT}).Decode(&doc)
	if err == nil {
		if veri, ok := doc["veri"].(bson.M); ok {
			raw, _ := bson.Marshal(veri)
			bson.Unmarshal(raw, &ayar)
			// Sıfır/boş değerleri varsayılana çek (bozuk kayıt koruması)
			vs := varsayilanAyar()
			if ayar.ChatModel == "" {
				ayar.ChatModel = vs.ChatModel
			}
			if ayar.EmbedModel == "" {
				ayar.EmbedModel = vs.EmbedModel
			}
			if ayar.NumCtx <= 0 {
				ayar.NumCtx = vs.NumCtx
			}
			if ayar.MaxToolIterasyon <= 0 {
				ayar.MaxToolIterasyon = vs.MaxToolIterasyon
			}
			if ayar.MaxChatPerUser <= 0 {
				ayar.MaxChatPerUser = vs.MaxChatPerUser
			}
			if ayar.RagLimit <= 0 {
				ayar.RagLimit = vs.RagLimit
			}
			if ayar.GecmisMesajSayisi <= 0 {
				ayar.GecmisMesajSayisi = vs.GecmisMesajSayisi
			}
		}
	}

	ayarCache = ayar
	ayarCacheZaman = time.Now()
	return ayar
}

func ayarCacheSifirla() {
	ayarMu.Lock()
	ayarCacheZaman = time.Time{}
	ayarMu.Unlock()
}

// gizlilikPromptu — Süper Yönetici olmayan kullanıcıların sistem promptuna eklenen sınırlar.
func gizlilikPromptu() string {
	return `

GİZLİLİK KURALLARI (EN YÜKSEK ÖNCELİK, DİĞER TÜM KURALLARI EZER):
- Bu kullanıcı sistem yöneticisi DEĞİLDİR.
- Sistem geneli istatistikleri (toplam kayıt/kullanıcı/cihaz sayısı, VT dağılımı, embed sayısı, aktif bağlantılar), sunucu/altyapı bilgilerini, model isimlerini ASLA paylaşma.
- Başka kullanıcıların kişisel verilerini, mail içeriklerini, giriş/çıkış kayıtlarını, denetim loglarını ASLA paylaşma.
- Yalnızca şunlar hakkında konuşabilirsin: kullanıcının KENDİ bilgileri, kendi soyağacı/akrabaları, açık paylaşımlar ve genel dokümantasyon.
- Yetki dışı bir soru gelirse kibarca "Bu bilgiye erişim yetkiniz bulunmuyor" de ve konuyu değiştir. Tahmin de yürütme.`
}

// bilesenBolumu — ayara göre bileşen talimatını döner (kapalıysa boş).
func bilesenBolumu() string {
	if ragAyar().BilesenlerAktif {
		return bilesenPromptu()
	}
	return ""
}

// bilesenPromptu — LLM'in sohbette dinamik bileşen gösterebilmesi için ortak talimat bloğu.
func bilesenPromptu() string {
	return `
DİNAMİK BİLEŞEN SİSTEMİ:
Yanıtlarında görsel veri göstermek için şu formatı kullan (satır başında, tek başına):
{{BILESEN:bilesen_id:gorunum}}
veya parametreli:
{{BILESEN:bilesen_id:gorunum:{"param":"deger"}}}

WIDGET bileşenleri (gorunum her zaman "widget", parametreler JSON):
- hmi-gauge: {{BILESEN:hmi-gauge:widget:{"deger":75,"min":0,"max":100}}} — gösterge ibresi
- hmi-sayac: {{BILESEN:hmi-sayac:widget:{"deger":1250,"etiket":"Toplam Kayıt"}}} — büyük sayı
- hmi-bar-grafik: {{BILESEN:hmi-bar-grafik:widget:{"veriler":[{"etiket":"Oca","deger":12},{"etiket":"Şub","deger":18}]}}}
- hmi-pasta-grafik: {{BILESEN:hmi-pasta-grafik:widget:{"veriler":[{"etiket":"A","deger":40,"renk":"#1890ff"},{"etiket":"B","deger":60,"renk":"#52c41a"}]}}}
- hmi-sparkline: {{BILESEN:hmi-sparkline:widget:{"veriler":[3,7,4,9,12,8]}}} — trend çizgisi
- hmi-ilerleme-halka: {{BILESEN:hmi-ilerleme-halka:widget:{"yuzde":68}}} — yüzde halkası
- hmi-durum-kart: {{BILESEN:hmi-durum-kart:widget:{"baslik":"Sunucu","aciklama":"Aktif","renk":"#52c41a"}}}
- hmi-mini-tablo: {{BILESEN:hmi-mini-tablo:widget:{"basliklar":["İsim","Adet"],"satirlar":[["Parça A",5],["Parça B",3]]}}}

KART bileşenleri (gorunum "paylasilmis_kart_sade", parametre gerekmez):
- cam-cnc-islem (CNC İşlem), cam-andon (Duruş/Yardım), cam-bakim (Bakım), cam-enerji (Enerji),
- cam-kalite (Kalite), cam-kutuk (Kütük), cam-vardiya (Vardiya), cam-parca (Cam Parça)
Örn: {{BILESEN:cam-cnc-islem:paylasilmis_kart_sade}}

KURALLAR:
- Bileşeni SADECE kullanıcı görsel/metrik/grafik isteğinde veya sayısal veri sunarken kullan.
- Gerçek veriyi tool sonuçlarından al, parametrelere gerçek değerleri yaz. Veri uydurma.
- Bileşen tokenının içine yorum yazma, JSON geçerli olsun.
- Normal metin yanıtlarında bileşen kullanma.`
}

// --- Admin Handler'ları ---

func ayarAdminKontrol(c fiber.Ctx) *FikoYTCtx.FikoYTCtx {
	fkRaw := c.Locals("FikoYTCtx")
	if fkRaw == nil {
		return nil
	}
	fk, ok := fkRaw.(*FikoYTCtx.FikoYTCtx)
	if !ok || fk == nil || fk.AktifKullanici == nil || !fk.AktifKullanici.YetkiVarmi("Super Yonetici") {
		return nil
	}
	return fk
}

func ayarGetirHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}
	return c.JSON(ragAyar())
}

func ayarKaydetHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}

	var istek RagAyar
	if err := json.Unmarshal(c.Body(), &istek); err != nil {
		return c.Status(400).JSON(fiber.Map{"hata": "Geçersiz istek gövdesi"})
	}
	if istek.ChatModel == "" || istek.EmbedModel == "" {
		return c.Status(400).JSON(fiber.Map{"hata": "Model alanları boş olamaz"})
	}

	eskiAyar := ragAyar()

	kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")
	veri, _ := bson.Marshal(istek)
	var veriM bson.M
	bson.Unmarshal(veri, &veriM)

	_, err := kol.UpdateOne(context.TODO(),
		bson.M{"vt": AyarlarVT},
		bson.M{
			"$set":         bson.M{"veri": veriM, "guncelleme": time.Now()},
			"$setOnInsert": bson.M{"vt": AyarlarVT, "olusturma": time.Now()},
		},
		options.Update().SetUpsert(true),
	)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"hata": err.Error()})
	}

	ayarCacheSifirla()

	// Embed modeli değiştiyse vektör cache geçersiz — yeniden gömme uyarısı döner
	embedDegisti := eskiAyar.EmbedModel != istek.EmbedModel
	if embedDegisti {
		cacheMu.Lock()
		vektorCache = nil
		cacheSonYukleme = time.Time{}
		cacheMu.Unlock()
		log.Printf("⚙️ RAG embed modeli değişti: %s → %s (yeniden gömme önerilir)", eskiAyar.EmbedModel, istek.EmbedModel)
	}

	// Canlı embed aç/kapa senkronizasyonu
	if istek.CanliEmbedAktif && !canliEmbedAktif {
		canliEmbedDurdurCh = make(chan struct{})
		go canliEmbedDinleyici()
	} else if !istek.CanliEmbedAktif && canliEmbedAktif {
		close(canliEmbedDurdurCh)
		canliEmbedAktif = false
	}

	log.Printf("⚙️ RAG ayarları güncellendi (chat: %s, embed: %s)", istek.ChatModel, istek.EmbedModel)

	return c.JSON(fiber.Map{
		"basarili":         true,
		"embedDegisti":     embedDegisti,
		"yenidenGomOneri":  embedDegisti,
	})
}

// modelYetenekleri — /api/show ile modelin yeteneklerini çeker (vision, embedding, thinking...).
func modelYetenekleri(isim string) []string {
	body, _ := json.Marshal(map[string]string{"model": isim})
	resp, err := ollamaClient.Post(OllamaURL+"/api/show", "application/json", bytes.NewReader(body))
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	var cevap struct {
		Capabilities []string `json:"capabilities"`
	}
	json.NewDecoder(resp.Body).Decode(&cevap)
	return cevap.Capabilities
}

// modelDusunebilirMi — model "thinking" yeteneğine sahip mi (kalıcı cache; yetenek model adına göre sabittir).
var (
	dusunmeCache   = map[string]bool{}
	dusunmeCacheMu sync.Mutex
)

func modelDusunebilirMi(model string) bool {
	dusunmeCacheMu.Lock()
	if v, ok := dusunmeCache[model]; ok {
		dusunmeCacheMu.Unlock()
		return v
	}
	dusunmeCacheMu.Unlock()

	destek := false
	for _, y := range modelYetenekleri(model) {
		if y == "thinking" {
			destek = true
			break
		}
	}

	dusunmeCacheMu.Lock()
	dusunmeCache[model] = destek
	dusunmeCacheMu.Unlock()
	return destek
}

// ayarModellerHandler — Ollama'daki kurulu modelleri yetenekleriyle listeler (admin model seçimi için).
func ayarModellerHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}

	resp, err := ollamaClient.Get(OllamaURL + "/api/tags")
	if err != nil {
		return c.Status(502).JSON(fiber.Map{"hata": "Ollama'ya ulaşılamadı: " + err.Error()})
	}
	defer resp.Body.Close()

	var cevap struct {
		Models []struct {
			Name    string `json:"name"`
			Size    int64  `json:"size"`
			Details struct {
				ParameterSize     string `json:"parameter_size"`
				QuantizationLevel string `json:"quantization_level"`
				Family            string `json:"family"`
			} `json:"details"`
		} `json:"models"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&cevap); err != nil {
		return c.Status(502).JSON(fiber.Map{"hata": "Ollama cevabı çözümlenemedi"})
	}

	var modeller []fiber.Map
	for _, m := range cevap.Models {
		yetenekler := modelYetenekleri(m.Name)
		embedMi := false
		visionMi := false
		for _, y := range yetenekler {
			if y == "embedding" {
				embedMi = true
			}
			if y == "vision" {
				visionMi = true
			}
		}
		if !embedMi && strings.Contains(strings.ToLower(m.Name), "embed") {
			embedMi = true
		}
		modeller = append(modeller, fiber.Map{
			"isim":      m.Name,
			"boyutGB":   fmt.Sprintf("%.1f", float64(m.Size)/1e9),
			"parametre": m.Details.ParameterSize,
			"quant":     m.Details.QuantizationLevel,
			"aile":      m.Details.Family,
			"embedMi":   embedMi,
			"visionMi":  visionMi,
		})
	}
	if modeller == nil {
		modeller = []fiber.Map{}
	}

	return c.JSON(fiber.Map{"modeller": modeller})
}

// --- Model İndirme / Silme (Ollama yönetimi) ---

// Büyük model indirmeleri 10 dk'lık genel timeout'a sığmaz — süresiz istemci.
var ollamaPullClient = &http.Client{}

// modelIndirHandler — Ollama pull ilerlemesini SSE olarak akıtır.
func modelIndirHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}

	var istek struct {
		Model string `json:"model"`
	}
	json.Unmarshal(c.Body(), &istek)
	istek.Model = strings.TrimSpace(istek.Model)
	if istek.Model == "" {
		return c.Status(400).JSON(fiber.Map{"hata": "model adı gerekli"})
	}

	c.Set("Content-Type", "text/event-stream")
	c.Set("Cache-Control", "no-cache")
	c.Set("Connection", "keep-alive")
	c.Set("X-Accel-Buffering", "no")

	model := istek.Model
	c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
		yaz := func(veri map[string]interface{}) {
			data, _ := json.Marshal(veri)
			fmt.Fprintf(w, "data: %s\n\n", data)
			w.Flush()
		}

		log.Printf("📥 Model indirme başladı: %s", model)
		body, _ := json.Marshal(map[string]interface{}{"model": model, "stream": true})
		resp, err := ollamaPullClient.Post(OllamaURL+"/api/pull", "application/json", bytes.NewReader(body))
		if err != nil {
			yaz(map[string]interface{}{"error": "Ollama'ya ulaşılamadı: " + err.Error()})
			return
		}
		defer resp.Body.Close()

		scanner := bufio.NewScanner(resp.Body)
		scanner.Buffer(make([]byte, 256*1024), 256*1024)
		for scanner.Scan() {
			satir := scanner.Bytes()
			if len(satir) == 0 {
				continue
			}
			fmt.Fprintf(w, "data: %s\n\n", satir)
			w.Flush()
		}
		log.Printf("📥 Model indirme bitti: %s", model)
	})

	return nil
}

func modelSilHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}

	var istek struct {
		Model string `json:"model"`
	}
	json.Unmarshal(c.Body(), &istek)
	if istek.Model == "" {
		return c.Status(400).JSON(fiber.Map{"hata": "model adı gerekli"})
	}

	ayar := ragAyar()
	if istek.Model == ayar.ChatModel || istek.Model == ayar.EmbedModel {
		return c.Status(400).JSON(fiber.Map{"hata": "Aktif kullanılan model silinemez. Önce başka model seçin."})
	}

	body, _ := json.Marshal(map[string]string{"model": istek.Model})
	req, _ := http.NewRequest("DELETE", OllamaURL+"/api/delete", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := ollamaClient.Do(req)
	if err != nil {
		return c.Status(502).JSON(fiber.Map{"hata": "Ollama'ya ulaşılamadı: " + err.Error()})
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		return c.Status(502).JSON(fiber.Map{"hata": fmt.Sprintf("Silinemedi (HTTP %d)", resp.StatusCode)})
	}

	log.Printf("🗑️ Model silindi: %s", istek.Model)
	return c.JSON(fiber.Map{"basarili": true})
}
