package rag

import (
	"context"
	"encoding/json"
	"log"
	"time"

	fiko_mongo "server/sistem/fikoyt_mongo"
	"server/sistem/socket_ortak"

	"github.com/gofiber/fiber/v3"
	"github.com/gofiber/fiber/v3/middleware/cors"
	"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"
)

var gommeDurum struct {
	Calisiyor      bool      `json:"calisiyor"`
	Toplam         int       `json:"toplam"`
	Islenen        int       `json:"islenen"`
	Hata           string    `json:"hata,omitempty"`
	SonGuncelleme  time.Time `json:"sonGuncelleme"`
	ChatDuraklatma bool      `json:"chatDuraklatma"`
}

func Ayarla(app *fiber.App) {
	ragGroup := app.Group("/rag")
	ragGroup.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"https://fikoyt.com", "https://www.fikoyt.com", "https://fikoyt.com:443", "https://www.fikoyt.com:443", "https://fikoyt.com:444", "https://fikoyt.com:446", "https://fikoyt.com:449", "https://fikoyt.com:9000", "https://www.fikoyt.com:9000", "https://fikoyt.com:4200", "https://localhost:4200", "https://fikoyt:4200", "https://localhost", "https://localhost:444", "https://localhost:446"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"*", "Content-Type", "Authorization"},
		AllowCredentials: true,
	}))

	ragGroup.Post("/ara", araHandler)
	ragGroup.Post("/sohbet", sohbetHandler)
	ragGroup.Post("/gomme/baslat", gommeBaslatHandler)
	ragGroup.Get("/gomme/durum", gommeDurumHandler)

	ragGroup.Get("/mcp/sse", mcpSSEHandler)
	ragGroup.Post("/mcp/messages", mcpMesajHandler)
	ragGroup.Post("/mcp", mcpStreamableHandler)

	ragGroup.Post("/konusma/yeni", konusmaYeniHandler)
	ragGroup.Get("/konusma/liste", konusmaListeHandler)
	ragGroup.Get("/konusma/:id", konusmaGetirHandler)
	ragGroup.Post("/konusma/mesaj", konusmaMesajHandler)
	ragGroup.Delete("/konusma/:id", konusmaSilHandler)

	ragGroup.Post("/gomme/canli/baslat", canliEmbedBaslatHandler)
	ragGroup.Post("/gomme/canli/durdur", canliEmbedDurdurHandler)
	ragGroup.Get("/gomme/canli/durum", canliEmbedDurumHandler)
	ragGroup.Post("/gomme/yenidenGom", yenidenGomHandler)

	// Streaming chat
	ragGroup.Post("/konusma/stream", konusmaStreamHandler)

	// Feedback
	ragGroup.Post("/konusma/feedback", feedbackHandler)

	// Analiz
	ragGroup.Get("/analiz/tool-kullanim", toolKullanimHandler)
	ragGroup.Get("/analiz/feedback", feedbackAnalizHandler)

	// Custom Tool yönetimi
	ragGroup.Get("/custom-tool/liste", customToolListeHandler)
	ragGroup.Post("/custom-tool/kaydet", customToolKaydetHandler)
	ragGroup.Delete("/custom-tool/:id", customToolSilHandler)
	ragGroup.Post("/custom-tool/test", customToolTestHandler)

	// Yetki yönetimi
	ragGroup.Get("/yetki/toollar", toolListeHandler)
	ragGroup.Get("/yetki/liste", yetkiListeHandler)
	ragGroup.Post("/yetki/kaydet", yetkiKaydetHandler)
	ragGroup.Delete("/yetki/:id", yetkiSilHandler)

	// Merkezi ayarlar (admin)
	ragGroup.Get("/ayarlar", ayarGetirHandler)
	ragGroup.Post("/ayarlar", ayarKaydetHandler)
	ragGroup.Get("/ayarlar/modeller", ayarModellerHandler)
	ragGroup.Post("/ayarlar/model-indir", modelIndirHandler)
	ragGroup.Post("/ayarlar/model-sil", modelSilHandler)

	log.Println("🧠 RAG modülü başlatılıyor...")

	go cihazOlayTemizleyici()
	go ragOtomatikBaslat()
}

func ragOtomatikBaslat() {
	time.Sleep(30 * time.Second)
	log.Println("🧠 RAG otomatik gömme başlatılıyor...")

	HareketleriGom(20)

	for gommeDurum.Calisiyor {
		time.Sleep(5 * time.Second)
	}

	if !ragAyar().CanliEmbedAktif {
		log.Println("🧠 RAG ilk gömme tamamlandı, canlı embed ayardan kapalı")
		return
	}
	log.Println("🧠 RAG ilk gömme tamamlandı, canlı embed dinleyici başlatılıyor...")
	canliEmbedDurdurCh = make(chan struct{})
	go canliEmbedDinleyici()
}

func araHandler(c fiber.Ctx) error {
	fk, err := kullaniciAl(c)
	if err != nil {
		return err
	}

	var istek struct {
		Sorgu string `json:"sorgu"`
		Limit int    `json:"limit"`
		VT    string `json:"vt"`
	}
	json.Unmarshal(c.Body(), &istek)

	if istek.Sorgu == "" {
		return c.Status(400).JSON(fiber.Map{"hata": "sorgu gerekli"})
	}
	if istek.Limit <= 0 {
		istek.Limit = 10
	}

	var sonuclar []bson.M
	if fk.AktifKullanici.YetkiVarmi("Super Yonetici") {
		sonuclar, err = AraMetin(istek.Sorgu, istek.Limit, istek.VT)
	} else {
		sonuclar, err = AraMetinIzinli(istek.Sorgu, istek.Limit, istek.VT, herkeseAcikVtListesi())
	}
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"hata": err.Error()})
	}

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

func sohbetHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok — sohbet için /rag/konusma uçlarını kullanın"})
	}

	var istek struct {
		Soru string `json:"soru"`
		VT   string `json:"vt"`
	}
	json.Unmarshal(c.Body(), &istek)

	if istek.Soru == "" {
		return c.Status(400).JSON(fiber.Map{"hata": "soru gerekli"})
	}

	cevap, kaynaklar, err := SoruSor(istek.Soru, istek.VT)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"hata": err.Error()})
	}

	return c.JSON(fiber.Map{
		"cevap":    cevap,
		"kaynaklar": kaynaklar,
	})
}

func gommeBaslatHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}
	var istek struct {
		Batch int `json:"batch"`
	}
	json.Unmarshal(c.Body(), &istek)

	if istek.Batch <= 0 {
		istek.Batch = 20
	}

	if gommeDurum.Calisiyor {
		return c.JSON(fiber.Map{"durum": "zaten çalışıyor", "ilerleme": gommeDurum})
	}

	HareketleriGom(istek.Batch)
	return c.JSON(fiber.Map{"durum": "başlatıldı"})
}

func gommeDurumHandler(c fiber.Ctx) error {
	chatSayacMu.Lock()
	aktifChat := chatSayac
	chatSayacMu.Unlock()

	cacheMu.Lock()
	cacheBoyut := len(vektorCache)
	cacheMu.Unlock()

	gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)
	gommeSayisi, _ := gommeKol.CountDocuments(context.TODO(), bson.M{})

	return c.JSON(fiber.Map{
		"calisiyor":      gommeDurum.Calisiyor,
		"toplam":         gommeDurum.Toplam,
		"islenen":        gommeDurum.Islenen,
		"hata":           gommeDurum.Hata,
		"sonGuncelleme":  gommeDurum.SonGuncelleme,
		"chatDuraklatma": aktifChat > 0,
		"aktifChat":      aktifChat,
		"cacheBoyut":     cacheBoyut,
		"gommeSayisi":    gommeSayisi,
		"embedModel":     ragAyar().EmbedModel,
		"chatModel":      ragAyar().ChatModel,
	})
}

var (
	sonBroadcast          time.Time
	canliEmbedAktif       bool
	otomatikGommeAktif    bool
	canliEmbedDurdurCh    chan struct{}
	otomatikGommeDurdurCh chan struct{}
)

func canliEmbedBaslatHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}
	if canliEmbedAktif {
		return c.JSON(fiber.Map{"durum": "zaten çalışıyor"})
	}
	canliEmbedDurdurCh = make(chan struct{})
	go canliEmbedDinleyici()
	return c.JSON(fiber.Map{"durum": "başlatıldı"})
}

func canliEmbedDurdurHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}
	if !canliEmbedAktif {
		return c.JSON(fiber.Map{"durum": "zaten durmuş"})
	}
	close(canliEmbedDurdurCh)
	canliEmbedAktif = false
	return c.JSON(fiber.Map{"durum": "durduruldu"})
}

func canliEmbedDurumHandler(c fiber.Ctx) error {
	return c.JSON(fiber.Map{
		"canliEmbed":    canliEmbedAktif,
		"otomatikGomme": otomatikGommeAktif,
	})
}

func ragDurumBroadcast() {
	if time.Since(sonBroadcast) < 3*time.Second {
		return
	}
	sonBroadcast = time.Now()

	if socket_ortak.FikoSocketIo == nil {
		return
	}

	chatSayacMu.Lock()
	aktifChat := chatSayac
	chatSayacMu.Unlock()

	cacheMu.Lock()
	cacheBoyut := len(vektorCache)
	cacheMu.Unlock()

	gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)
	gommeSayisi, _ := gommeKol.CountDocuments(context.TODO(), bson.M{})

	socket_ortak.FikoSocketIo.Emit("FikoYT.sistem.rag.durum", bson.M{
		"calisiyor":      gommeDurum.Calisiyor,
		"toplam":         gommeDurum.Toplam,
		"islenen":        gommeDurum.Islenen,
		"hata":           gommeDurum.Hata,
		"chatDuraklatma": aktifChat > 0,
		"aktifChat":      aktifChat,
		"cacheBoyut":     cacheBoyut,
		"gommeSayisi":    gommeSayisi,
		"embedModel":     ragAyar().EmbedModel,
		"chatModel":      ragAyar().ChatModel,
	})
}

func otomatikGommeDongusu() {
	otomatikGommeAktif = true
	defer func() { otomatikGommeAktif = false }()

	for {
		select {
		case <-otomatikGommeDurdurCh:
			return
		default:
		}
		if !gommeDurum.Calisiyor {
			HareketleriGom(10)
		}
		time.Sleep(3 * time.Minute)
	}
}

func canliEmbedDinleyici() {
	canliEmbedAktif = true
	defer func() { canliEmbedAktif = false }()

	for {
		select {
		case <-canliEmbedDurdurCh:
			log.Println("🔄 Canlı embed dinleyici durduruldu")
			return
		default:
		}
		func() {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("Change stream panic: %v", r)
				}
			}()

			kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")
			gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)

			pipeline := mongo.Pipeline{
				{{Key: "$match", Value: bson.M{
					"operationType": bson.M{"$in": bson.A{"insert", "update", "replace"}},
				}}},
			}

			opts := options.ChangeStream().SetFullDocument(options.UpdateLookup)
			stream, err := kol.Watch(context.TODO(), pipeline, opts)
			if err != nil {
				log.Printf("Change stream hatası: %v — 30s sonra tekrar deneniyor", err)
				time.Sleep(30 * time.Second)
				return
			}
			defer stream.Close(context.TODO())

			log.Println("🔄 Canlı embed dinleyici başlatıldı (Change Stream)")

			for stream.Next(context.TODO()) {
				var event bson.M
				stream.Decode(&event)

				fullDoc, ok := event["fullDocument"].(bson.M)
				if !ok {
					continue
				}

				hid, ok := fullDoc["_id"].(primitive.ObjectID)
				if !ok {
					continue
				}

				metin := hareketMetniOlustur(fullDoc)
				if len(metin) < 10 {
					continue
				}

				gommeler, err := ollamaEmbed([]string{metin})
				if err != nil || len(gommeler) == 0 {
					log.Printf("Canlı embed hatası (ID: %s): %v", hid.Hex(), err)
					continue
				}

				vt, _ := fullDoc["vt"].(string)

				gommeKol.UpdateOne(context.TODO(),
					bson.M{"hareket_id": hid},
					bson.M{"$set": bson.M{
						"hareket_id": hid,
						"vt":         vt,
						"metin":      metin,
						"gomme":      gommeler[0],
						"olusturma":  time.Now(),
					}},
					options.Update().SetUpsert(true),
				)

				log.Printf("⚡ Canlı embed: %s [%s]", hid.Hex(), vt)
			}
		}()

		log.Println("Change stream kapandı, 10s sonra yeniden bağlanıyor...")
		time.Sleep(10 * time.Second)
	}
}

const maxCihazOlayKayit = 150
const maxGirisCikisKayit = 20
const maxMailAuditKayit = 20

func cihazOlayTemizleyici() {
	time.Sleep(60 * time.Second)
	for {
		cihazOlayTemizle()
		girisCikisTemizle()
		mailAuditTemizle()
		geciciDosyaTemizle()
		time.Sleep(30 * time.Minute)
	}
}

func girisCikisTemizle() {
	ctx := context.TODO()
	kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")
	gommeKol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection(GommeKoleksiyon)

	pipeline := mongo.Pipeline{
		{{Key: "$match", Value: bson.M{"vt": "FikoYT.sistem.giris_cikis"}}},
		{{Key: "$group", Value: bson.M{"_id": "$personelId", "sayi": bson.M{"$sum": 1}}}},
		{{Key: "$match", Value: bson.M{"sayi": bson.M{"$gt": maxGirisCikisKayit}}}},
	}

	cursor, err := kol.Aggregate(ctx, pipeline)
	if err != nil {
		return
	}
	defer cursor.Close(ctx)

	toplamSilinen := int64(0)
	for cursor.Next(ctx) {
		var doc bson.M
		cursor.Decode(&doc)

		personelId := doc["_id"]
		sayi, _ := doc["sayi"].(int32)
		silinecek := int(sayi) - maxGirisCikisKayit
		if silinecek <= 0 {
			continue
		}

		eskiCursor, err := kol.Find(ctx, bson.M{
			"vt":         "FikoYT.sistem.giris_cikis",
			"personelId": personelId,
		}, options.Find().SetSort(bson.M{"_id": 1}).SetLimit(int64(silinecek)).SetProjection(bson.M{"_id": 1}))
		if err != nil {
			continue
		}

		var silinecekIdler []primitive.ObjectID
		for eskiCursor.Next(ctx) {
			var d bson.M
			eskiCursor.Decode(&d)
			if id, ok := d["_id"].(primitive.ObjectID); ok {
				silinecekIdler = append(silinecekIdler, id)
			}
		}
		eskiCursor.Close(ctx)

		if len(silinecekIdler) > 0 {
			sonuc, _ := kol.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": silinecekIdler}})
			gommeKol.DeleteMany(ctx, bson.M{"hareket_id": bson.M{"$in": silinecekIdler}})
			if sonuc != nil {
				toplamSilinen += sonuc.DeletedCount
			}
		}
	}

	if toplamSilinen > 0 {
		log.Printf("🧹 Giriş/çıkış temizliği: %d eski kayıt silindi (limit: %d/kullanıcı)", toplamSilinen, maxGirisCikisKayit)
	}
}

func cihazOlayTemizle() {
	ctx := context.TODO()
	kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")

	// Cihaz bazında gruplama — her cihazın kaç olayı var
	pipeline := mongo.Pipeline{
		{{Key: "$match", Value: bson.M{"vt": "FikoYT.sistem.cihaz.olaylar"}}},
		{{Key: "$group", Value: bson.M{"_id": "$kartUid", "sayi": bson.M{"$sum": 1}}}},
		{{Key: "$match", Value: bson.M{"sayi": bson.M{"$gt": maxCihazOlayKayit}}}},
	}

	cursor, err := kol.Aggregate(ctx, pipeline)
	if err != nil {
		return
	}
	defer cursor.Close(ctx)

	toplamSilinen := int64(0)
	for cursor.Next(ctx) {
		var doc bson.M
		cursor.Decode(&doc)

		kartUid := doc["_id"]
		sayi, _ := doc["sayi"].(int32)
		silinecek := int(sayi) - maxCihazOlayKayit

		if silinecek <= 0 {
			continue
		}

		// En eski kayıtları bul ve sil
		eskiCursor, err := kol.Find(ctx, bson.M{
			"vt":      "FikoYT.sistem.cihaz.olaylar",
			"kartUid": kartUid,
		}, options.Find().SetSort(bson.M{"_id": 1}).SetLimit(int64(silinecek)).SetProjection(bson.M{"_id": 1}))

		if err != nil {
			continue
		}

		var silinecekIdler []primitive.ObjectID
		for eskiCursor.Next(ctx) {
			var d bson.M
			eskiCursor.Decode(&d)
			if id, ok := d["_id"].(primitive.ObjectID); ok {
				silinecekIdler = append(silinecekIdler, id)
			}
		}
		eskiCursor.Close(ctx)

		if len(silinecekIdler) > 0 {
			sonuc, _ := kol.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": silinecekIdler}})
			if sonuc != nil {
				toplamSilinen += sonuc.DeletedCount
			}
		}
	}

	if toplamSilinen > 0 {
		log.Printf("🧹 Cihaz olay temizliği: %d eski kayıt silindi (limit: %d/cihaz)", toplamSilinen, maxCihazOlayKayit)
	}
}

func mailAuditTemizle() {
	ctx := context.TODO()
	kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")

	pipeline := mongo.Pipeline{
		{{Key: "$match", Value: bson.M{"vt": "FikoYT.sistem.mail.audit"}}},
		{{Key: "$group", Value: bson.M{"_id": "$hesapId", "sayi": bson.M{"$sum": 1}}}},
		{{Key: "$match", Value: bson.M{"sayi": bson.M{"$gt": maxMailAuditKayit}}}},
	}

	cursor, err := kol.Aggregate(ctx, pipeline)
	if err != nil {
		return
	}
	defer cursor.Close(ctx)

	toplamSilinen := int64(0)
	for cursor.Next(ctx) {
		var doc bson.M
		cursor.Decode(&doc)

		hesapId := doc["_id"]
		sayi, _ := doc["sayi"].(int32)
		silinecek := int(sayi) - maxMailAuditKayit
		if silinecek <= 0 {
			continue
		}

		eskiCursor, err := kol.Find(ctx, bson.M{
			"vt":       "FikoYT.sistem.mail.audit",
			"hesapId":  hesapId,
		}, options.Find().SetSort(bson.M{"_id": 1}).SetLimit(int64(silinecek)).SetProjection(bson.M{"_id": 1}))
		if err != nil {
			continue
		}

		var silinecekIdler []primitive.ObjectID
		for eskiCursor.Next(ctx) {
			var d bson.M
			eskiCursor.Decode(&d)
			if id, ok := d["_id"].(primitive.ObjectID); ok {
				silinecekIdler = append(silinecekIdler, id)
			}
		}
		eskiCursor.Close(ctx)

		if len(silinecekIdler) > 0 {
			sonuc, _ := kol.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": silinecekIdler}})
			if sonuc != nil {
				toplamSilinen += sonuc.DeletedCount
			}
		}
	}

	if toplamSilinen > 0 {
		log.Printf("🧹 Mail audit temizliği: %d eski kayıt silindi (limit: %d/hesap)", toplamSilinen, maxMailAuditKayit)
	}
}

func geciciDosyaTemizle() {
	ctx := context.TODO()
	kol := fiko_mongo.GetMongoDBClient().Database("fikoyt").Collection("hareketler")

	esik := primitive.NewObjectIDFromTimestamp(time.Now().Add(-1 * time.Hour))
	sonuc, err := kol.DeleteMany(ctx, bson.M{
		"vt":  "FikoYT.sistem.geciciDosyalar",
		"_id": bson.M{"$lt": esik},
	})
	if err != nil {
		return
	}
	if sonuc.DeletedCount > 0 {
		log.Printf("🧹 Geçici dosya temizliği: %d eski kayıt silindi", sonuc.DeletedCount)
	}
}

func yenidenGomHandler(c fiber.Ctx) error {
	if ayarAdminKontrol(c) == nil {
		return c.Status(403).JSON(fiber.Map{"hata": "Yetkiniz yok"})
	}
	var istek struct {
		VT string `json:"vt"`
	}
	json.Unmarshal(c.Body(), &istek)

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

	// vt boş → TÜM gömmeler silinip baştan üretilir (embed modeli değişince gerekli)
	filtre := bson.M{}
	if istek.VT != "" {
		filtre = bson.M{"vt": istek.VT}
	}
	silSonuc, err := gommeKol.DeleteMany(ctx, filtre)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"hata": err.Error()})
	}

	cacheMu.Lock()
	vektorCache = nil
	cacheSonYukleme = time.Time{}
	cacheMu.Unlock()

	go HareketleriGom(20)

	return c.JSON(fiber.Map{
		"durum":       "yeniden gömme başlatıldı",
		"silinenSayi": silSonuc.DeletedCount,
		"vt":          istek.VT,
	})
}
