package rag

import (
	"context"
	"fmt"
	"log"
	"math"
	"strings"
	"sync"
	"time"

	fiko_mongo "server/sistem/fikoyt_mongo"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

const GommeKoleksiyon = "rag_gomme"

// Chat istekleri geldiğinde embedding'i otomatik duraklatma
var (
	chatAktif    sync.Mutex
	chatSayac    int32
	chatSayacMu  sync.Mutex
)

func ChatBasladi() {
	chatSayacMu.Lock()
	chatSayac++
	chatSayacMu.Unlock()
}

func ChatBitti() {
	chatSayacMu.Lock()
	if chatSayac > 0 {
		chatSayac--
	}
	chatSayacMu.Unlock()
}

func chatBekle() {
	for {
		chatSayacMu.Lock()
		aktif := chatSayac
		chatSayacMu.Unlock()
		if aktif == 0 {
			return
		}
		time.Sleep(500 * time.Millisecond)
	}
}

// --- ObjectID Çözümleme Cache ---
var (
	isimCache   = map[string]string{}
	isimCacheMu sync.RWMutex
)

func objectIdCozumle(id string) string {
	if len(id) != 24 {
		return ""
	}
	for _, c := range id {
		if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
			return ""
		}
	}

	isimCacheMu.RLock()
	if isim, ok := isimCache[id]; ok {
		isimCacheMu.RUnlock()
		return isim
	}
	isimCacheMu.RUnlock()

	ctx := context.TODO()
	db := fiko_mongo.GetMongoDBClient().Database("fikoyt")
	oid, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return ""
	}

	var sonuc string

	// soyAgaciKisiler'de ara
	var kisi bson.M
	db.Collection("hareketler").FindOne(ctx, bson.M{"_id": oid, "vt": "FikoYT.sistem.soyAgaciKisiler"}).Decode(&kisi)
	if kisi != nil {
		if v, ok := kisi["veri"].(bson.M); ok {
			isim, _ := v["isim"].(string)
			soyIsim, _ := v["soyIsim"].(string)
			sonuc = strings.TrimSpace(isim + " " + soyIsim)
		}
	}

	if sonuc == "" {
		// platformKullaniciId ile ara
		db.Collection("hareketler").FindOne(ctx, bson.M{"vt": "FikoYT.sistem.soyAgaciKisiler", "veri.platformKullaniciId": id}).Decode(&kisi)
		if kisi != nil {
			if v, ok := kisi["veri"].(bson.M); ok {
				isim, _ := v["isim"].(string)
				soyIsim, _ := v["soyIsim"].(string)
				sonuc = strings.TrimSpace(isim + " " + soyIsim)
			}
		}
	}

	if sonuc == "" {
		// goUyeler'de ara
		var uye bson.M
		db.Collection("goUyeler").FindOne(ctx, bson.M{"_id": oid}).Decode(&uye)
		if uye != nil {
			isim, _ := uye["isim"].(string)
			soyIsim, _ := uye["soyIsim"].(string)
			sonuc = strings.TrimSpace(isim + " " + soyIsim)
		}
	}

	if sonuc == "" {
		// firmalar'da ara
		var firma bson.M
		db.Collection("hareketler").FindOne(ctx, bson.M{"_id": oid, "vt": "FikoYT.sistem.firmalar"}).Decode(&firma)
		if firma != nil {
			if v, ok := firma["veri"].(bson.M); ok {
				ad, _ := v["firma_adi"].(string)
				if ad == "" {
					ad, _ = v["ad"].(string)
				}
				sonuc = ad
			}
		}
	}

	if sonuc == "" {
		// goCihazlar'da ara
		var cihaz bson.M
		db.Collection("goCihazlar").FindOne(ctx, bson.M{"_id": oid}).Decode(&cihaz)
		if cihaz != nil {
			isim, _ := cihaz["ismi"].(string)
			if isim == "" {
				isim, _ = cihaz["cihaz_ismi"].(string)
			}
			kimlik, _ := cihaz["kimlik"].(string)
			if isim != "" {
				sonuc = isim
				if kimlik != "" {
					sonuc += " (" + kimlik + ")"
				}
			} else if kimlik != "" {
				sonuc = kimlik
			}
		}
	}

	if sonuc == "" {
		// hareketler'de genel ara (departman, personel vb.)
		var genel bson.M
		db.Collection("hareketler").FindOne(ctx, bson.M{"_id": oid}).Decode(&genel)
		if genel != nil {
			if v, ok := genel["veri"].(bson.M); ok {
				// İsim alanlarını dene
				for _, alan := range []string{"isim", "ad", "baslik", "firma_adi", "departman_adi", "cihaz_adi", "vardiya_adi", "yetki_adi"} {
					if val, ok := v[alan].(string); ok && val != "" {
						sonuc = val
						// Soyisim varsa ekle
						if si, ok := v["soyIsim"].(string); ok && si != "" {
							sonuc += " " + si
						} else if si, ok := v["soyad"].(string); ok && si != "" {
							sonuc += " " + si
						}
						break
					}
				}
			}
		}
	}

	isimCacheMu.Lock()
	isimCache[id] = sonuc
	if len(isimCache) > 5000 {
		isimCache = map[string]string{}
	}
	isimCacheMu.Unlock()

	return sonuc
}

func degerYaz(sb *strings.Builder, key string, val interface{}) {
	idStr := ""
	switch v := val.(type) {
	case primitive.ObjectID:
		idStr = v.Hex()
	case string:
		if len(v) == 24 {
			idStr = v
		}
	}

	if idStr != "" {
		isim := objectIdCozumle(idStr)
		if isim != "" {
			sb.WriteString(fmt.Sprintf("%s: %s [%s]\n", key, isim, idStr))
			return
		}
	}

	sb.WriteString(fmt.Sprintf("%s: %v\n", key, val))
}

func idIsimCozumle(val interface{}) string {
	idStr := ""
	switch v := val.(type) {
	case primitive.ObjectID:
		idStr = v.Hex()
	case string:
		idStr = v
	}
	if idStr == "" {
		return ""
	}
	return objectIdCozumle(idStr)
}

func aileBagiAciklama(kisi1, kisi2, tur, tersTur string) string {
	var sb strings.Builder
	sb.WriteString("[Akrabalık Bağı]\n")

	switch tur {
	case "BABA":
		sb.WriteString(fmt.Sprintf("%s'nin babası %s'dir.\n", kisi1, kisi2))
		sb.WriteString(fmt.Sprintf("%s, %s'nin çocuğudur.\n", kisi1, kisi2))
	case "ANNE":
		sb.WriteString(fmt.Sprintf("%s'nin annesi %s'dir.\n", kisi1, kisi2))
		sb.WriteString(fmt.Sprintf("%s, %s'nin çocuğudur.\n", kisi1, kisi2))
	case "COCUK":
		sb.WriteString(fmt.Sprintf("%s'nin çocuğu %s'dir.\n", kisi1, kisi2))
		sb.WriteString(fmt.Sprintf("%s'nin ebeveyni %s'dir.\n", kisi2, kisi1))
	case "ES":
		sb.WriteString(fmt.Sprintf("%s ile %s eştir (evli).\n", kisi1, kisi2))
	case "KARDES":
		sb.WriteString(fmt.Sprintf("%s ile %s kardeştir.\n", kisi1, kisi2))
	case "YEGEN":
		sb.WriteString(fmt.Sprintf("%s'nin yeğeni %s'dir.\n", kisi1, kisi2))
	case "AMCA":
		sb.WriteString(fmt.Sprintf("%s'nin amcası %s'dir.\n", kisi1, kisi2))
	case "HALA":
		sb.WriteString(fmt.Sprintf("%s'nin halası %s'dir.\n", kisi1, kisi2))
	case "TEYZE":
		sb.WriteString(fmt.Sprintf("%s'nin teyzesi %s'dir.\n", kisi1, kisi2))
	case "DAYI":
		sb.WriteString(fmt.Sprintf("%s'nin dayısı %s'dir.\n", kisi1, kisi2))
	case "KUZEN":
		sb.WriteString(fmt.Sprintf("%s ile %s kuzendir.\n", kisi1, kisi2))
	case "TORUN":
		sb.WriteString(fmt.Sprintf("%s'nin torunu %s'dir.\n", kisi1, kisi2))
	case "DEDE":
		sb.WriteString(fmt.Sprintf("%s'nin dedesi %s'dir.\n", kisi1, kisi2))
	case "BABAANNE":
		sb.WriteString(fmt.Sprintf("%s'nin babaannesi %s'dir.\n", kisi1, kisi2))
	case "ANNEANNE":
		sb.WriteString(fmt.Sprintf("%s'nin anneannesi %s'dir.\n", kisi1, kisi2))
	default:
		if tur != "" {
			sb.WriteString(fmt.Sprintf("%s'nin %s ilişkisi: %s\n", kisi1, strings.ToLower(tur), kisi2))
		}
	}

	return sb.String()
}

func hareketMetniOlustur(hareket bson.M) string {
	var sb strings.Builder

	vt, _ := hareket["vt"].(string)
	if vt != "" {
		sb.WriteString(fmt.Sprintf("[%s]\n", vt))
	}

	// aileBaglari kayıtları için özel, yön belirten metin üret
	if vt == "FikoYT.sistem.aileBaglari" {
		if veri, ok := hareket["veri"].(bson.M); ok {
			kisi1Isim := idIsimCozumle(veri["kisi1"])
			kisi2Isim := idIsimCozumle(veri["kisi2"])
			tur, _ := veri["akrabalikTuru"].(string)
			tersTur, _ := veri["tersAkrabalikTuru"].(string)

			if kisi1Isim == "" {
				kisi1Isim = "?"
			}
			if kisi2Isim == "" {
				if elle, ok := veri["elleGirilenKisi"].(bson.M); ok {
					isim, _ := elle["isim"].(string)
					soyIsim, _ := elle["soyIsim"].(string)
					kisi2Isim = strings.TrimSpace(isim + " " + soyIsim)
				}
			}
			if kisi2Isim == "" {
				kisi2Isim = "?"
			}

			sb.WriteString(aileBagiAciklama(kisi1Isim, kisi2Isim, tur, tersTur))
		}
		return sb.String()
	}

	// sahip alanını çözümle
	if sahip := hareket["sahip"]; sahip != nil {
		degerYaz(&sb, "sahip", sahip)
	}

	if veri, ok := hareket["veri"].(bson.M); ok {
		bsonYazZengin(&sb, veri, "", vt)
	}

	if icerikler, ok := hareket["icerikler"].(bson.A); ok {
		for _, ic := range icerikler {
			if icMap, ok := ic.(bson.M); ok {
				if metin, ok := icMap["metin"].(string); ok && metin != "" {
					sb.WriteString(fmt.Sprintf("İçerik: %s\n", metin))
				} else if html, ok := icMap["html"].(string); ok && html != "" {
					sb.WriteString(fmt.Sprintf("İçerik: %s\n", htmlTemizle(html)))
				}
				// İçerikteki parametreleri de yaz
				if params, ok := icMap["parametreler"].(bson.A); ok {
					for _, p := range params {
						if pm, ok := p.(bson.M); ok {
							baslik, _ := pm["baslik"].(string)
							deger := pm["degeri"]
							if baslik != "" && deger != nil {
								degerYaz(&sb, baslik, deger)
							}
						}
					}
				}
			}
		}
	}

	if ekler, ok := hareket["ekler"].(bson.A); ok {
		for _, ek := range ekler {
			if ekMap, ok := ek.(bson.M); ok {
				bsonYazZengin(&sb, ekMap, "ek.", vt)
			}
		}
	}

	if dosyalar, ok := hareket["dosyalar"].(bson.A); ok {
		for _, d := range dosyalar {
			if dMap, ok := d.(bson.M); ok {
				if ismi, ok := dMap["ismi"].(string); ok {
					sb.WriteString(fmt.Sprintf("Dosya: %s\n", ismi))
				}
			}
		}
	}

	// parametreler (üst seviye)
	if params, ok := hareket["parametreler"].(bson.A); ok {
		for _, p := range params {
			if pm, ok := p.(bson.M); ok {
				baslik, _ := pm["baslik"].(string)
				deger := pm["degeri"]
				if baslik != "" && deger != nil {
					degerYaz(&sb, baslik, deger)
				}
			}
		}
	}

	return sb.String()
}

// ID referanslarını çözümleyerek yazar
var idAlanlar = map[string]bool{
	"kisi1": true, "kisi2": true, "sahip": true,
	"kullanici_id": true, "platformKullaniciId": true, "kullanici_kimlik": true,
	"firma_id": true, "firmaId": true, "firma": true,
	"personel_id": true, "personel": true,
	"yonetici_id": true, "yonetici": true,
	"departman_id": true, "departman": true,
	"onaylayan_id": true, "onaylayan": true,
	"musteri_id": true, "musteri": true,
	"tedarikci_id": true, "tedarikci": true,
	"atanan": true, "olusturan": true,
	"cihaz_id": true, "cihaz": true,
	"hedef_kullanici": true, "gonderen": true, "alici": true,
	"kullanici": true, "sorumlu": true,
}

func bsonYazZengin(sb *strings.Builder, m bson.M, prefix string, vt string) {
	for k, v := range m {
		if k == "_id" || k == "gomme" || k == "parola" || k == "epostaOnayKodu" {
			continue
		}

		anahtar := prefix + k

		switch val := v.(type) {
		case bson.M:
			bsonYazZengin(sb, val, anahtar+".", vt)
		case bson.A:
			if len(val) > 20 {
				sb.WriteString(fmt.Sprintf("%s: [%d öğe]\n", anahtar, len(val)))
				continue
			}
			for i, item := range val {
				if itemMap, ok := item.(bson.M); ok {
					bsonYazZengin(sb, itemMap, fmt.Sprintf("%s[%d].", anahtar, i), vt)
				} else {
					degerYaz(sb, fmt.Sprintf("%s[%d]", anahtar, i), item)
				}
			}
		case primitive.ObjectID:
			isim := objectIdCozumle(val.Hex())
			if isim != "" {
				sb.WriteString(fmt.Sprintf("%s: %s\n", anahtar, isim))
			} else {
				sb.WriteString(fmt.Sprintf("%s: %s\n", anahtar, val.Hex()))
			}
		default:
			if idAlanlar[k] {
				degerYaz(sb, anahtar, val)
			} else {
				sb.WriteString(fmt.Sprintf("%s: %v\n", anahtar, val))
			}
		}
	}
}

// --- Sonuç Zenginleştirme (MCP + RAG cevapları için) ---

func sonucZenginlestir(doc bson.M) bson.M {
	if doc == nil {
		return doc
	}
	sonuc := bson.M{}
	for k, v := range doc {
		sonuc[k] = degerZenginlestir(k, v)
	}
	return sonuc
}

func sonuclarZenginlestir(docs []bson.M) []bson.M {
	for i := range docs {
		docs[i] = sonucZenginlestir(docs[i])
	}
	return docs
}

// objectIdTamCozumle — ID'nin arkasındaki tüm kayıt verisini getirir
func objectIdTamCozumle(id string) bson.M {
	if len(id) != 24 {
		return nil
	}

	ctx := context.TODO()
	db := fiko_mongo.GetMongoDBClient().Database("fikoyt")
	oid, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return nil
	}

	// soyAgaciKisiler
	var doc bson.M
	db.Collection("hareketler").FindOne(ctx, bson.M{"_id": oid, "vt": "FikoYT.sistem.soyAgaciKisiler"}).Decode(&doc)
	if doc != nil {
		if v, ok := doc["veri"].(bson.M); ok {
			v["_kaynak"] = "soyAgaciKisiler"
			v["_id"] = id
			return v
		}
	}

	// platformKullaniciId ile soyağacında
	db.Collection("hareketler").FindOne(ctx, bson.M{"vt": "FikoYT.sistem.soyAgaciKisiler", "veri.platformKullaniciId": id}).Decode(&doc)
	if doc != nil {
		if v, ok := doc["veri"].(bson.M); ok {
			v["_kaynak"] = "soyAgaciKisiler"
			v["_id"] = id
			return v
		}
	}

	// goUyeler
	var uye bson.M
	db.Collection("goUyeler").FindOne(ctx, bson.M{"_id": oid}, options.FindOne().SetProjection(bson.M{"parola": 0, "epostaOnayKodu": 0})).Decode(&uye)
	if uye != nil {
		uye["_kaynak"] = "goUyeler"
		return uye
	}

	// goCihazlar
	var cihaz bson.M
	db.Collection("goCihazlar").FindOne(ctx, bson.M{"_id": oid}).Decode(&cihaz)
	if cihaz != nil {
		cihaz["_kaynak"] = "goCihazlar"
		return cihaz
	}

	// hareketler genel
	var genel bson.M
	db.Collection("hareketler").FindOne(ctx, bson.M{"_id": oid}).Decode(&genel)
	if genel != nil {
		sonuc := bson.M{"_id": id}
		if vt, ok := genel["vt"].(string); ok {
			sonuc["_vt"] = vt
		}
		if v, ok := genel["veri"].(bson.M); ok {
			for mk, mv := range v {
				sonuc[mk] = mv
			}
		}
		return sonuc
	}

	return nil
}

func degerZenginlestir(key string, val interface{}) interface{} {
	switch v := val.(type) {
	case primitive.ObjectID:
		hex := v.Hex()
		tam := objectIdTamCozumle(hex)
		if tam != nil {
			return tam
		}
		return hex
	case bson.M:
		zengin := bson.M{}
		for mk, mv := range v {
			zengin[mk] = degerZenginlestir(mk, mv)
		}
		return zengin
	case bson.A:
		if len(v) > 50 {
			return fmt.Sprintf("[%d öğe]", len(v))
		}
		zengin := make(bson.A, len(v))
		for i, item := range v {
			zengin[i] = degerZenginlestir("", item)
		}
		return zengin
	case string:
		if len(v) == 24 && idAlanlar[key] {
			tam := objectIdTamCozumle(v)
			if tam != nil {
				return tam
			}
		}
		return v
	default:
		return v
	}
}

func htmlTemizle(html string) string {
	result := html
	for {
		start := strings.Index(result, "<")
		if start == -1 {
			break
		}
		end := strings.Index(result[start:], ">")
		if end == -1 {
			break
		}
		result = result[:start] + result[start+end+1:]
	}
	return strings.TrimSpace(result)
}

func HareketleriGom(batchBoyut int) {
	if gommeDurum.Calisiyor {
		return
	}
	gommeDurum.Calisiyor = true
	gommeDurum.Islenen = 0
	gommeDurum.Hata = ""

	go func() {
		defer func() { gommeDurum.Calisiyor = false }()

		ctx := context.TODO()
		hareketKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")
		gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)

		mevcutCursor, err := gommeKol.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"hareket_id": 1}))
		if err != nil {
			gommeDurum.Hata = err.Error()
			return
		}
		mevcutIDler := map[primitive.ObjectID]bool{}
		for mevcutCursor.Next(ctx) {
			var doc bson.M
			mevcutCursor.Decode(&doc)
			if hid, ok := doc["hareket_id"].(primitive.ObjectID); ok {
				mevcutIDler[hid] = true
			}
		}
		mevcutCursor.Close(ctx)

		toplam, _ := hareketKol.CountDocuments(ctx, bson.M{})
		gommeDurum.Toplam = int(toplam)

		cursor, err := hareketKol.Find(ctx, bson.M{})
		if err != nil {
			gommeDurum.Hata = err.Error()
			return
		}
		defer cursor.Close(ctx)

		var batch []batchItem

		for cursor.Next(ctx) {
			var hareket bson.M
			cursor.Decode(&hareket)

			hid, _ := hareket["_id"].(primitive.ObjectID)
			if mevcutIDler[hid] {
				gommeDurum.Islenen++
				continue
			}

			metin := hareketMetniOlustur(hareket)
			if len(metin) < 10 {
				gommeDurum.Islenen++
				continue
			}

			vt, _ := hareket["vt"].(string)
			batch = append(batch, batchItem{hid, vt, metin})

			if len(batch) >= batchBoyut {
				chatBekle()
				if err := batchGom(gommeKol, batch); err != nil {
					log.Printf("RAG gömme batch hata: %v", err)
				}
				gommeDurum.Islenen += len(batch)
				batch = batch[:0]
				ragDurumBroadcast()
				time.Sleep(200 * time.Millisecond)
			}
		}

		if len(batch) > 0 {
			if err := batchGom(gommeKol, batch); err != nil {
				log.Printf("RAG gömme son batch hata: %v", err)
			}
			gommeDurum.Islenen += len(batch)
		}

		log.Printf("RAG gömme tamamlandı: %d/%d kayıt", gommeDurum.Islenen, gommeDurum.Toplam)
	}()
}

type batchItem struct {
	id    primitive.ObjectID
	vt    string
	metin string
}

func batchGom(kol *mongo.Collection, batch []batchItem) error {
	metinler := make([]string, len(batch))
	for i, b := range batch {
		metin := b.metin
		if len(metin) > 8000 {
			metin = metin[:8000]
		}
		metinler[i] = metin
	}

	gommeler, err := ollamaEmbed(metinler)
	if err != nil {
		return err
	}

	var docs []interface{}
	for i, b := range batch {
		if i >= len(gommeler) {
			break
		}
		docs = append(docs, bson.M{
			"hareket_id": b.id,
			"vt":         b.vt,
			"metin":      b.metin,
			"gomme":      gommeler[i],
			"olusturma":  time.Now(),
		})
	}

	if len(docs) > 0 {
		_, err = kol.InsertMany(context.TODO(), docs)
	}
	return err
}

func AraMetin(sorgu string, limit int, vtFiltre string) ([]bson.M, error) {
	return AraMetinIzinli(sorgu, limit, vtFiltre, nil)
}

// AraMetinIzinli — izinliVtler doluysa yalnızca o vt önekleriyle eşleşen kayıtlarda arar
// (Süper Yönetici olmayan kullanıcılar için gizlilik sınırı).
func AraMetinIzinli(sorgu string, limit int, vtFiltre string, izinliVtler []string) ([]bson.M, error) {
	gommeler, err := ollamaEmbed([]string{sorgu})
	if err != nil {
		return nil, err
	}
	if len(gommeler) == 0 {
		return nil, fmt.Errorf("embedding oluşturulamadı")
	}

	return vektorAra(gommeler[0], limit, vtFiltre, izinliVtler)
}

func vtIzinliMi(vt string, izinliVtler []string) bool {
	for _, izinli := range izinliVtler {
		if strings.HasPrefix(vt, izinli) {
			return true
		}
	}
	return false
}

func rerank(sorgu string, adaylar []bson.M, limit int) []bson.M {
	if len(adaylar) <= limit {
		return adaylar
	}

	var ozetler strings.Builder
	for i, a := range adaylar {
		metin, _ := a["metin"].(string)
		if len(metin) > 200 {
			metin = guvenliKes(metin, 200)
		}
		ozetler.WriteString(fmt.Sprintf("[%d] %s\n", i, metin))
	}

	prompt := fmt.Sprintf(
		"Aşağıdaki kayıtlardan hangisi şu soruyla en alakalı: \"%s\"\n\n%s\n\nSadece en alakalı %d tanesinin numaralarını virgülle yaz (örn: 3,0,7,1,5). Başka bir şey yazma.",
		sorgu, ozetler.String(), limit,
	)

	cevap, err := ollamaChat([]map[string]string{
		{"role": "user", "content": prompt},
	})

	if err != nil {
		log.Printf("Reranker hatası, orijinal sıralama kullanılıyor: %v", err)
		return adaylar[:limit]
	}

	var sirali []bson.M
	gorulenler := map[int]bool{}
	for _, parca := range strings.Split(strings.TrimSpace(cevap), ",") {
		parca = strings.TrimSpace(parca)
		var idx int
		if _, err := fmt.Sscanf(parca, "%d", &idx); err == nil && idx >= 0 && idx < len(adaylar) && !gorulenler[idx] {
			sirali = append(sirali, adaylar[idx])
			gorulenler[idx] = true
		}
		if len(sirali) >= limit {
			break
		}
	}

	if len(sirali) < limit {
		for _, a := range adaylar {
			if len(sirali) >= limit {
				break
			}
			zatenVar := false
			for _, s := range sirali {
				if s["_id"] == a["_id"] {
					zatenVar = true
					break
				}
			}
			if !zatenVar {
				sirali = append(sirali, a)
			}
		}
	}

	return sirali
}

func vektorAra(sorguGomme []float32, limit int, vtFiltre string, izinliVtler []string) ([]bson.M, error) {
	gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)
	return vektorAraBruteForce(gommeKol, sorguGomme, limit, vtFiltre, izinliVtler)
}

func vektorAraMongot(gommeKol *mongo.Collection, sorguGomme []float32, limit int, vtFiltre string) ([]bson.M, error) {
	vectorSearch := bson.M{
		"index":         "vektor_index",
		"path":          "gomme",
		"queryVector":   sorguGomme,
		"numCandidates": limit * 10,
		"limit":         limit,
	}
	if vtFiltre != "" {
		vectorSearch["filter"] = bson.M{"vt": vtFiltre}
	}

	pipeline := mongo.Pipeline{
		{{Key: "$vectorSearch", Value: vectorSearch}},
		{{Key: "$addFields", Value: bson.M{"skor": bson.M{"$meta": "vectorSearchScore"}}}},
		{{Key: "$project", Value: bson.M{"gomme": 0}}},
		{{Key: "$lookup", Value: bson.M{
			"from":         "hareketler",
			"localField":   "hareket_id",
			"foreignField": "_id",
			"as":           "hareket",
		}}},
		{{Key: "$unwind", Value: bson.M{"path": "$hareket", "preserveNullAndEmptyArrays": true}}},
	}

	cursor, err := gommeKol.Aggregate(context.TODO(), pipeline)
	if err != nil {
		return nil, err
	}
	var sonuclar []bson.M
	cursor.All(context.TODO(), &sonuclar)
	return sonuclar, nil
}

// --- In-Memory Vektör Cache ---

type cacheKayit struct {
	id        primitive.ObjectID
	hareketId primitive.ObjectID
	vt        string
	metin     string
	gomme     []float32
}

var (
	vektorCache     []cacheKayit
	cacheSonYukleme time.Time
	cacheMu         sync.Mutex
)

func cacheYukle() {
	cacheMu.Lock()
	defer cacheMu.Unlock()

	if time.Since(cacheSonYukleme) < 60*time.Second && len(vektorCache) > 0 {
		return
	}

	ctx := context.TODO()
	gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)

	cursor, err := gommeKol.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{
		"hareket_id": 1, "vt": 1, "metin": 1, "gomme": 1,
	}))
	if err != nil {
		log.Printf("Cache yükleme hatası: %v", err)
		return
	}
	defer cursor.Close(ctx)

	var yeniCache []cacheKayit
	for cursor.Next(ctx) {
		var doc bson.M
		cursor.Decode(&doc)

		gommeRaw, ok := doc["gomme"]
		if !ok {
			continue
		}

		var gomme []float32
		if arr, ok := gommeRaw.(bson.A); ok {
			gomme = make([]float32, len(arr))
			for i, val := range arr {
				switch n := val.(type) {
				case float64:
					gomme[i] = float32(n)
				case float32:
					gomme[i] = n
				}
			}
		}

		if len(gomme) == 0 {
			continue
		}

		id, _ := doc["_id"].(primitive.ObjectID)
		hid, _ := doc["hareket_id"].(primitive.ObjectID)
		vt, _ := doc["vt"].(string)
		metin, _ := doc["metin"].(string)

		yeniCache = append(yeniCache, cacheKayit{id, hid, vt, metin, gomme})
	}

	vektorCache = yeniCache
	cacheSonYukleme = time.Now()
	log.Printf("Vektör cache yüklendi: %d kayıt", len(vektorCache))
}

type skorluKayit struct {
	skor float64
	doc  bson.M
}

func vektorAraBruteForce(gommeKol *mongo.Collection, sorguGomme []float32, limit int, vtFiltre string, izinliVtler []string) ([]bson.M, error) {
	cacheYukle()

	var enIyiler []skorluKayit

	for i := range vektorCache {
		ck := &vektorCache[i]

		if vtFiltre != "" && ck.vt != vtFiltre {
			continue
		}
		if izinliVtler != nil && !vtIzinliMi(ck.vt, izinliVtler) {
			continue
		}

		skor := cosineSimilarity(sorguGomme, ck.gomme)

		if len(enIyiler) < limit {
			enIyiler = append(enIyiler, skorluKayit{skor, bson.M{
				"_id": ck.id, "hareket_id": ck.hareketId, "vt": ck.vt, "metin": ck.metin, "skor": skor,
			}})
		} else if skor > enIyiler[len(enIyiler)-1].skor {
			enIyiler[len(enIyiler)-1] = skorluKayit{skor, bson.M{
				"_id": ck.id, "hareket_id": ck.hareketId, "vt": ck.vt, "metin": ck.metin, "skor": skor,
			}}
		} else {
			continue
		}

		for j := len(enIyiler) - 1; j > 0 && enIyiler[j].skor > enIyiler[j-1].skor; j-- {
			enIyiler[j], enIyiler[j-1] = enIyiler[j-1], enIyiler[j]
		}
	}

	ctx := context.TODO()
	hareketKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")
	sonuclar := make([]bson.M, 0, len(enIyiler))
	for _, sk := range enIyiler {
		hid, _ := sk.doc["hareket_id"].(primitive.ObjectID)
		var hareket bson.M
		hareketKol.FindOne(ctx, bson.M{"_id": hid}).Decode(&hareket)
		sk.doc["hareket"] = hareket
		sonuclar = append(sonuclar, sk.doc)
	}

	return sonuclar, nil
}

func cosineSimilarity(a, b []float32) float64 {
	if len(a) != len(b) {
		return 0
	}
	var dot, normA, normB float32
	for i := range a {
		dot += a[i] * b[i]
		normA += a[i] * a[i]
		normB += b[i] * b[i]
	}
	if normA == 0 || normB == 0 {
		return 0
	}
	return float64(dot) / (math.Sqrt(float64(normA)) * math.Sqrt(float64(normB)))
}

func SoruSor(soru string, vtFiltre string) (string, []bson.M, error) {
	ChatBasladi()
	defer ChatBitti()

	sonuclar, err := AraMetin(soru, 30, vtFiltre)
	if err != nil {
		return "", nil, err
	}

	var baglam strings.Builder
	for i, s := range sonuclar {
		if metin, ok := s["metin"].(string); ok {
			baglam.WriteString(fmt.Sprintf("--- Kayıt %d ---\n%s\n", i+1, metin))
		}
	}

	mesajlar := []map[string]string{
		{
			"role":    "system",
			"content": "Sen FikoYT üretim yönetim sisteminin yapay zeka asistanısın. Görevin veritabanındaki kayıtları analiz edip kullanıcının sorularını yanıtlamak.\n\nKurallar:\n- Verilen bağlamdaki kayıtları derinlemesine analiz et, doğrudan bilgi olmasa bile mevcut verilerden çıkarım yap.\n- Kayıtlardaki tüm alanları (veri, içerikler, ekler, dosyalar, tarihler, durumlar) incele.\n- Sayısal veriler varsa hesapla, listele, karşılaştır.\n- Yanıtlarını Türkçe ver, kısa ve net ol.\n- Emin olmadığın bilgileri 'muhtemelen' veya 'kayıtlara göre' diye belirt.",
		},
		{
			"role":    "user",
			"content": fmt.Sprintf("Bağlam:\n%s\n\nSoru: %s", baglam.String(), soru),
		},
	}

	cevap, err := ollamaChat(mesajlar)
	if err != nil {
		return "", sonuclar, err
	}

	return cevap, sonuclar, nil
}
