package rag

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

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

const (
	MaxToolIterasyon = 5
	ToolTimeout      = 30 * time.Second
	MaxAgentSure     = 3 * time.Minute
	MaxToolSonucLen  = 4000
)

type AgentAdim struct {
	Tip     string                 `json:"tip" bson:"tip"`
	ToolAdi string                 `json:"tool_adi,omitempty" bson:"tool_adi,omitempty"`
	Args    map[string]interface{} `json:"args,omitempty" bson:"args,omitempty"`
	Sonuc   string                 `json:"sonuc,omitempty" bson:"sonuc,omitempty"`
	SureMs  int64                  `json:"sure_ms,omitempty" bson:"sure_ms,omitempty"`
	Hata    string                 `json:"hata,omitempty" bson:"hata,omitempty"`
	Zaman   time.Time              `json:"zaman" bson:"zaman"`
}

type AIYetenekler struct {
	FunctionCalling bool
	Thinking        bool
	Streaming       bool
	MaxCtx          int
}

type StreamYazici func(tip string, veri map[string]interface{})

var toolCallRegex = regexp.MustCompile(`\[TOOL_CALL:\s*(\{[^]]*\})\s*\]`)

// --- Agent Döngüsü ---

func agentDongusu(baglam *SohbetBaglam, streamYaz StreamYazici) (cevapMetin string, dusunceMetin string, adimlar []AgentAdim) {
	agentBaslangic := time.Now()
	yetkiler := KullaniciYetkileriniAl(baglam.Fk.AktifKullanici.IsleObjectId().Id)
	yetenekler := modelYetenekleriAl()

	isim := fmt.Sprintf("%v", baglam.Fk.AktifKullanici.Isim)
	soyIsim := fmt.Sprintf("%v", baglam.Fk.AktifKullanici.SoyIsim)
	superMi := baglam.Fk.AktifKullanici.YetkiVarmi("Super Yonetici")

	sistemPrompt := agentSistemPromptu(isim, soyIsim, yetkiler, yetenekler, superMi)

	mesajlar := []map[string]string{
		{"role": "system", "content": sistemPrompt},
	}
	mesajlar = append(mesajlar, baglam.Mesajlar[1:]...)

	var tamDusunce strings.Builder
	var tamCevap strings.Builder
	toolSayaci := 0
	sonToolArgs := ""
	maxIter := ragAyar().MaxToolIterasyon

	for iterasyon := 0; iterasyon < maxIter+1; iterasyon++ {
		if time.Since(agentBaslangic) > MaxAgentSure {
			log.Printf("⏰ Agent timeout: %v", time.Since(agentBaslangic))
			break
		}

		sonIterasyon := iterasyon == maxIter
		var iterasyonYanit strings.Builder
		var iterasyonDusunce strings.Builder

		var iterResimler []string
		if iterasyon == 0 {
			iterResimler = baglam.Resimler
		}
		err := ollamaChatStream(mesajlar, iterResimler, func(tip string, parca string) {
			if tip == "d" {
				iterasyonDusunce.WriteString(parca)
				tamDusunce.WriteString(parca)
				streamYaz("d", map[string]interface{}{"m": parca})
			} else {
				iterasyonYanit.WriteString(parca)
			}
		})

		if err != nil {
			log.Printf("Agent Ollama hatası: %v", err)
			streamYaz("hata", map[string]interface{}{"m": "AI yanıt hatası: " + err.Error()})
			break
		}

		yanit := iterasyonYanit.String()

		if sonIterasyon {
			tamCevap.WriteString(yanit)
			for _, parca := range parcala(yanit, 50) {
				streamYaz("p", map[string]interface{}{"m": parca})
			}
			break
		}

		toolCagrilari := toolCallParse(yanit)

		if len(toolCagrilari) == 0 {
			tamCevap.WriteString(yanit)
			for _, parca := range parcala(yanit, 50) {
				streamYaz("p", map[string]interface{}{"m": parca})
			}
			break
		}

		for _, tc := range toolCagrilari {
			if toolSayaci >= maxIter {
				streamYaz("p", map[string]interface{}{"m": "Maksimum tool çağrısı limitine ulaşıldı."})
				tamCevap.WriteString("Maksimum tool çağrısı limitine ulaşıldı.")
				break
			}

			argsJSON, _ := json.Marshal(tc.Args)
			argsStr := string(argsJSON)
			if argsStr == sonToolArgs {
				log.Printf("🔄 Aynı tool+args tekrarı algılandı, döngü kırılıyor")
				break
			}
			sonToolArgs = argsStr

			if !ToolYetkisiVarMi(baglam.Fk.AktifKullanici.IsleObjectId().Id, tc.ToolAdi) {
				hataMsj := fmt.Sprintf("%s tool'u için yetkiniz yok.", tc.ToolAdi)
				adimlar = append(adimlar, AgentAdim{Tip: "tool_cagri", ToolAdi: tc.ToolAdi, Args: tc.Args, Hata: hataMsj, Zaman: time.Now()})
				streamYaz("tool_sonuc", map[string]interface{}{"tool": tc.ToolAdi, "sonuc": hataMsj, "sure_ms": 0})

				mesajlar = append(mesajlar,
					map[string]string{"role": "assistant", "content": yanit},
					map[string]string{"role": "user", "content": fmt.Sprintf("[TOOL_SONUC: %s] Hata: %s", tc.ToolAdi, hataMsj)},
				)
				continue
			}

			streamYaz("tool", map[string]interface{}{"tool": tc.ToolAdi, "args": tc.Args})
			adimlar = append(adimlar, AgentAdim{Tip: "tool_cagri", ToolAdi: tc.ToolAdi, Args: tc.Args, Zaman: time.Now()})

			sonuc, sureMs, toolErr := agentToolCalistir(tc.ToolAdi, tc.Args, baglam.KullaniciIdStr)
			toolSayaci++

			if toolErr != nil {
				hataMsj := toolErr.Error()
				adimlar = append(adimlar, AgentAdim{Tip: "tool_sonuc", ToolAdi: tc.ToolAdi, Hata: hataMsj, SureMs: sureMs, Zaman: time.Now()})
				streamYaz("tool_sonuc", map[string]interface{}{"tool": tc.ToolAdi, "sonuc": "Hata: " + hataMsj, "sure_ms": sureMs})

				mesajlar = append(mesajlar,
					map[string]string{"role": "assistant", "content": yanit},
					map[string]string{"role": "user", "content": fmt.Sprintf("[TOOL_SONUC: %s] Hata: %s", tc.ToolAdi, hataMsj)},
				)
			} else {
				adimlar = append(adimlar, AgentAdim{Tip: "tool_sonuc", ToolAdi: tc.ToolAdi, Sonuc: sonuc, SureMs: sureMs, Zaman: time.Now()})
				streamYaz("tool_sonuc", map[string]interface{}{"tool": tc.ToolAdi, "sonuc": sonuc, "sure_ms": sureMs})

				mesajlar = append(mesajlar,
					map[string]string{"role": "assistant", "content": yanit},
					map[string]string{"role": "user", "content": fmt.Sprintf("[TOOL_SONUC: %s]\n%s", tc.ToolAdi, sonuc)},
				)
			}
		}
	}

	cevapMetin = tamCevap.String()
	dusunceMetin = tamDusunce.String()
	return
}

// --- Tool Call Parse ---

type ToolCall struct {
	ToolAdi string
	Args    map[string]interface{}
}

func toolCallParse(metin string) []ToolCall {
	eslesmeleler := toolCallRegex.FindAllStringSubmatch(metin, -1)
	var sonuc []ToolCall

	for _, e := range eslesmeleler {
		if len(e) < 2 {
			continue
		}
		var tc struct {
			Tool string                 `json:"tool"`
			Args map[string]interface{} `json:"args"`
		}
		if err := json.Unmarshal([]byte(e[1]), &tc); err != nil {
			log.Printf("Tool call parse hatası: %v — %s", err, e[1])
			continue
		}
		if tc.Tool != "" {
			if tc.Args == nil {
				tc.Args = map[string]interface{}{}
			}
			sonuc = append(sonuc, ToolCall{ToolAdi: tc.Tool, Args: tc.Args})
		}
	}
	return sonuc
}

// --- Tool Çalıştırma ---

func agentToolCalistir(toolAdi string, args map[string]interface{}, kullaniciId string) (string, int64, error) {
	baslangic := time.Now()

	ctx, cancel := context.WithTimeout(context.Background(), ToolTimeout)
	defer cancel()

	doneCh := make(chan jsonRpcCevap, 1)
	go func() {
		argsJSON, _ := json.Marshal(args)
		istek := jsonRpcIstek{
			Jsonrpc: "2.0",
			ID:      "agent",
			Method:  "tools/call",
			Params:  json.RawMessage(fmt.Sprintf(`{"name":"%s","arguments":%s}`, toolAdi, string(argsJSON))),
		}
		doneCh <- mcpToolCagir(istek)
	}()

	select {
	case cevap := <-doneCh:
		sureMs := time.Since(baslangic).Milliseconds()
		metin := mcpCevapMetinAl(cevap)
		if len(metin) > MaxToolSonucLen {
			metin = guvenliKes(metin, MaxToolSonucLen) + "\n... (kırpıldı)"
		}
		go toolKullanimLogla(toolAdi, kullaniciId, sureMs, cevap.Error == nil, "")
		return metin, sureMs, nil
	case <-ctx.Done():
		sureMs := time.Since(baslangic).Milliseconds()
		go toolKullanimLogla(toolAdi, kullaniciId, sureMs, false, "timeout")
		return "", sureMs, fmt.Errorf("tool %s zaman aşımına uğradı (%ds)", toolAdi, int(ToolTimeout.Seconds()))
	}
}

// --- MCP Cevap Metin Çıkarma ---

func mcpCevapMetinAl(cevap jsonRpcCevap) string {
	if cevap.Error != nil {
		return "Hata: " + cevap.Error.Message
	}
	if cevap.Result == nil {
		return ""
	}

	resultMap, ok := cevap.Result.(fiber.Map)
	if !ok {
		data, _ := json.Marshal(cevap.Result)
		return string(data)
	}

	contentRaw, ok := resultMap["content"]
	if !ok {
		data, _ := json.Marshal(resultMap)
		return string(data)
	}

	contentArr, ok := contentRaw.([]fiber.Map)
	if !ok {
		data, _ := json.Marshal(contentRaw)
		return string(data)
	}

	var sb strings.Builder
	for _, item := range contentArr {
		if text, ok := item["text"].(string); ok {
			sb.WriteString(text)
		}
	}
	return sb.String()
}

// --- Model Yetenekleri ---

func modelYetenekleriAl() AIYetenekler {
	ayar := ragAyar()
	return AIYetenekler{
		FunctionCalling: false,
		Thinking:        ayar.DusunmeAktif,
		Streaming:       true,
		MaxCtx:          ayar.NumCtx,
	}
}

// --- Sistem Promptu ---

func agentSistemPromptu(isim, soyIsim string, yetkiler map[string]bool, yetenekler AIYetenekler, superMi bool) string {
	toolAciklamalari := mcpToolAciklamalariOlustur(yetkiler)

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf(`Sen FikoYT üretim yönetim sisteminin AI agent'ısın. Kullanıcı: %s %s.

TEMEL KURALLAR:
1. HER ZAMAN TÜRKÇE CEVAP VER.
2. DOĞAL DİL KULLAN — sanki kendi bilginmiş gibi konuş.
3. Kısa ve net cevaplar ver.
4. Emin olmadığın konularda yumuşak ifadeler kullan.

AGENT MODU:
Sen bir AI agent'sın. Soruyu cevaplamak için veritabanını sorgulayabilir, hesaplama yapabilir ve birden fazla adım atabilirsin.

TOOL KULLANIM KURALLARI:
- Veri gerektiğinde aşağıdaki formatta tool çağır:
  [TOOL_CALL: {"tool": "tool_adi", "args": {"parametre": "deger"}}]
- Tool sonucunu [TOOL_SONUC: tool_adi] formatında alacaksın
- Sonucu analiz et, gerekirse başka tool çağır
- Maksimum %d tool çağrısı yapabilirsin
- Basit sohbet/selamlaşma mesajlarında TOOL ÇAĞIRMA, direkt cevap ver
- Tool çağırmadan cevaplayabildiğin soruları direkt cevapla

YAZMA İŞLEMLERİ (hareket_ekle, hareket_guncelle, hareket_sil):
- Bu tool'lar veri değiştirir, kullanmadan ÖNCE kullanıcıya açıkla ve onay iste
- Onay almadan ASLA yazma işlemi yapma

`, isim, soyIsim, ragAyar().MaxToolIterasyon))

	sb.WriteString("KULLANILABİLİR TOOL'LAR:\n")
	sb.WriteString(toolAciklamalari)

	ayar := ragAyar()
	if ayar.BilesenlerAktif {
		sb.WriteString(bilesenPromptu())
		sb.WriteString("\n")
	}

	sb.WriteString(`
AKRABALIK TERİMLERİ:
- AMCA = babanın erkek kardeşi, DAYI = annenin erkek kardeşi
- HALA = babanın kız kardeşi, TEYZE = annenin kız kardeşi
- KAYINVALIDE = eşin annesi, KAYINPEDER = eşin babası
`)

	if !superMi {
		sb.WriteString(gizlilikPromptu())
		sb.WriteString("\n")
	}

	if ayar.SistemPromptEk != "" {
		sb.WriteString("\nYÖNETİCİ TALİMATLARI:\n")
		sb.WriteString(ayar.SistemPromptEk)
		sb.WriteString("\n")
	}

	return sb.String()
}

// --- Tool Açıklamaları ---

func mcpToolAciklamalariOlustur(yetkiler map[string]bool) string {
	toollar := mcpToolTanimlari()
	toollar = append(toollar, customToolMCPTanimlari()...)
	var sb strings.Builder
	sira := 1

	for _, tool := range toollar {
		isim, _ := tool["name"].(string)
		if !yetkiler[isim] {
			continue
		}
		aciklama, _ := tool["description"].(string)
		if len(aciklama) > 120 {
			aciklama = guvenliKes(aciklama, 120) + "..."
		}

		sb.WriteString(fmt.Sprintf("%d. %s: %s\n", sira, isim, aciklama))

		if schema, ok := tool["inputSchema"].(fiber.Map); ok {
			if props, ok := schema["properties"].(fiber.Map); ok {
				var paramlar []string
				required := map[string]bool{}
				if req, ok := schema["required"].([]string); ok {
					for _, r := range req {
						required[r] = true
					}
				}
				for pIsim, pDef := range props {
					if pd, ok := pDef.(fiber.Map); ok {
						tip, _ := pd["type"].(string)
						zorunlu := ""
						if required[pIsim] {
							zorunlu = ", zorunlu"
						}
						paramlar = append(paramlar, fmt.Sprintf("%s(%s%s)", pIsim, tip, zorunlu))
					}
				}
				if len(paramlar) > 0 {
					sb.WriteString(fmt.Sprintf("   Args: %s\n", strings.Join(paramlar, ", ")))
				}
			}
		}
		sira++
	}
	return sb.String()
}

// --- Yardımcılar ---

// parcala — rune bazlı böler; bayt bazlı kesim UTF-8 (Türkçe) karakterleri ortadan bölüp � üretir.
func parcala(metin string, boyut int) []string {
	runes := []rune(metin)
	if len(runes) <= boyut {
		return []string{metin}
	}
	var parcalar []string
	for i := 0; i < len(runes); i += boyut {
		son := i + boyut
		if son > len(runes) {
			son = len(runes)
		}
		parcalar = append(parcalar, string(runes[i:son]))
	}
	return parcalar
}

// guvenliKes — metni en fazla maxRune karaktere UTF-8 güvenli kısaltır.
func guvenliKes(metin string, maxRune int) string {
	runes := []rune(metin)
	if len(runes) <= maxRune {
		return metin
	}
	return string(runes[:maxRune])
}

func adimlarToBson(adimlar []AgentAdim) bson.A {
	sonuc := bson.A{}
	for _, a := range adimlar {
		sonuc = append(sonuc, bson.M{
			"tip":      a.Tip,
			"tool_adi": a.ToolAdi,
			"args":     a.Args,
			"sonuc":    a.Sonuc,
			"sure_ms":  a.SureMs,
			"hata":     a.Hata,
			"zaman":    a.Zaman,
		})
	}
	return sonuc
}
