package main

import (
	"context"
	"log/slog"
	"strings"

	"fiko-ai/internal/conversation"
	"fiko-ai/internal/knowledge"
	"fiko-ai/internal/llm"
	"fiko-ai/internal/skill"
	"fiko-ai/internal/tool"
	"fiko-ai/internal/workflow"
)

// Agent Builder'ın (Faz 9) ihtiyaç duyduğu yeteneklerin GERÇEK uygulamaları burada
// bağlanır (workflow paketi bu paketleri bilmez; küçük arayüzler ister):
//
//	LLM      → ollama sağlayıcısı (tek turluk üretim)
//	Tools    → tool.Executor (yetki + doğrulama İÇİNDE denetlenir)
//	Knowledge→ bilgi tabanı araması (parça metinleri)
//	Memory   → konuşma geçmişi (son N mesaj)
//	Skills   → sorguya uyan skill'i bul ve bağlı tool'unu çalıştır
//
// GÜVENLİK: akış, ajanın aşamayacağı bir kapıyı AŞAMAZ. Tool çalıştırması Executor'a
// gider (yetki, risk kapıları, denetim kaydı) ve kullanıcı kimliği her çağrıda
// aktarılır: yetki bu kimliğe göre denetlenir.

// ---- LLM ----

// workflowLLM tek turluk metin üretimi yapar (akış sohbet DEĞİLDİR: akış yoktur).
// Model/temperature boşsa AYARDAKİ değerler kullanılır — aynı politika ajanla.
// Not: settings.LLMOptions yalnızca üretim parametrelerini verir; model adı ayrı
// okunur (set.Model) çünkü llm.Options model taşımaz.
type workflowLLM struct {
	provider llm.Provider
	model    func() string
	opts     func() llm.Options
}

func (w workflowLLM) Complete(ctx context.Context, req workflow.LLMRequest) (workflow.LLMResult, error) {
	o := w.opts()
	// Model önceliği: node'un istediği > ayardaki kayıtlı model > sağlayıcı varsayılanı.
	model := ""
	if w.model != nil {
		model = strings.TrimSpace(w.model())
	}
	if m := strings.TrimSpace(req.Model); m != "" {
		model = m
	}
	if req.Temperature != nil {
		t := *req.Temperature
		o.Temperature = &t
	}
	res, err := w.provider.Generate(ctx, llm.Request{
		Model: model,
		Messages: []llm.Message{
			// Sistem istemi ayrı mesaj olarak: sağlayıcı yönergeleri istemle karıştırmasın.
			{Role: llm.RoleSystem, Content: req.System},
			{Role: llm.RoleUser, Content: req.Prompt},
		},
		Options: llm.Options{
			Temperature: o.Temperature,
			TopP:        o.TopP,
			MaxTokens:   o.MaxTokens,
			ContextSize: o.ContextSize,
		},
	})
	if err != nil {
		return workflow.LLMResult{}, err
	}
	return workflow.LLMResult{
		Text:             res.Content,
		PromptTokens:     res.PromptTokens,
		CompletionTokens: res.CompletionTokens,
	}, nil
}

// ---- Tool ----

// workflowTools tool çalıştırmasını Executor'a devreder. Actor "agent" DEĞİL "user":
// akış bir tool çağrısını ajan kararı olarak değil, kullanıcının talebi olarak başlatır;
// yetki yine de aynı şekilde (kullanıcı kimliğine göre) denetlenir.
type workflowTools struct{ exec *tool.Executor }

func (t workflowTools) RunTool(ctx context.Context, userID, name string, input map[string]any) (map[string]any, error) {
	res, err := t.exec.Execute(ctx, tool.Request{
		Ref:    name,
		Input:  input,
		Actor:  "user",
		UserID: userID,
	})
	if err != nil {
		return nil, err
	}
	return res.Output, nil
}

// ---- RAG ----

// workflowRetriever bilgi tabanından en ilgili parça METİNLERİNİ döndürür.
// Node yalnızca metin bekler; skor/başlık taşınmaz (motor metin işler).
type workflowRetriever struct {
	svc      *knowledge.Service
	minScore float64
}

func (r workflowRetriever) Passages(ctx context.Context, userID, query string, limit int) ([]string, error) {
	hits, err := r.svc.Search(ctx, knowledge.SearchOptions{
		UserID:    userID,
		Query:     query,
		Mode:      knowledge.ModeHybrid,
		Limit:     limit,
		MinScore:  r.minScore,
		ProjectID: "", // akış proje kavramı bilmez: TÜM belgeler taranır
	})
	if err != nil {
		return nil, err
	}
	out := make([]string, 0, len(hits))
	for _, h := range hits {
		if t := strings.TrimSpace(h.Text); t != "" {
			out = append(out, t)
		}
	}
	return out, nil
}

// ---- Memory ----

// workflowHistory konuşmanın son mesajlarını ESKİDEN YENİYE döndürür (motor öyle bekler).
// Yalnızca tamamlanmış mesajlar alınır: yarım/kesilen yanıtlar bağlama girmemeli.
type workflowHistory struct{ svc *conversation.Service }

func (h workflowHistory) Recent(ctx context.Context, userID, conversationID string, limit int) ([]workflow.MemoryMessage, error) {
	msgs, err := h.svc.Messages(ctx, userID, conversationID, limit)
	if err != nil {
		return nil, err
	}
	out := make([]workflow.MemoryMessage, 0, len(msgs))
	for _, m := range msgs {
		if m.Status != conversation.StatusComplete || strings.TrimSpace(m.Content) == "" {
			continue
		}
		role := "user"
		if m.Role == conversation.RoleAssistant {
			role = "assistant"
		}
		out = append(out, workflow.MemoryMessage{Role: role, Content: m.Content})
	}
	return out, nil
}

// ---- Skill ----

// workflowSkillRunner sorguya en uygun skill'i bulur ve BAĞLI TOOL'UNU çalıştırır
// (skill'in kendi yürütücüsü değil). Eşleşme yoksa workflow.ErrNoSkill döner: motor
// bunu "bu node çalışamadı" olarak kullanıcıya gösterir, sessizce boş geçmez.
type workflowSkillRunner struct {
	svc *skill.Service
}

func (r workflowSkillRunner) RunSkill(ctx context.Context, _, query string, args map[string]any) (workflow.SkillRun, error) {
	res, err := r.svc.Search(ctx, skill.SearchOptions{Query: query, Mode: skill.ModeHybrid, Limit: 1})
	if err != nil {
		return workflow.SkillRun{}, err
	}
	if len(res) == 0 {
		return workflow.SkillRun{}, workflow.ErrNoSkill
	}
	sk := res[0].Skill
	if strings.TrimSpace(sk.ID) == "" {
		return workflow.SkillRun{}, workflow.ErrNoSkill
	}
	// args: akış node'unun geçirdiği parametreler; boş olabilir (skill varsayılanları uygulanır).
	out, err := r.svc.ExecuteSkill(ctx, sk.ID, args)
	if err != nil {
		return workflow.SkillRun{}, err
	}
	return workflow.SkillRun{Skill: out.SkillName, Tool: out.Tool, Output: out.Output}, nil
}

// workflowDeps gerçek uygulamaları workflow.Deps'e çevirir.
//
// Bağlı OLMAYAN yetenekler bilinçli olarak boş bırakılabilir: akış paketi bunları
// "bağlı değil" sayar ve o türdeki node'ları ÇALIŞTIRILAMAZ yapar (kaydı engellemez).
// Böylece eksik bir alt sistemde sunucu ayakta kalır, yalnızca o akış çalıştırılamaz.
func workflowDeps(p llm.Provider, ts *toolStack, kb *knowledge.Service, conv *conversation.Service,
	sk *skill.Service, model func() string, opts func() llm.Options) workflow.Deps {

	d := workflow.Deps{
		LLM:    workflowLLM{provider: p, model: model, opts: opts},
		Memory: workflowHistory{svc: conv},
	}
	if ts != nil && ts.executor != nil {
		d.Tools = workflowTools{exec: ts.executor}
	}
	if kb != nil {
		d.Knowledge = workflowRetriever{svc: kb, minScore: knowledgeMinScore}
	}
	if sk != nil {
		d.Skills = workflowSkillRunner{svc: sk}
	}
	return d
}

// logUnavailableWorkflowNodes bağlı olmayan node türlerini AÇIKÇA loglar: kullanıcı
// palette bakıp çalıştıramadığı bir düğüm gördüğünde nedenini aramasın.
// (Meta uç bu bilgiyi arayüze de verir; log sunucu tarafında iz için.)
func logUnavailableWorkflowNodes(d workflow.Deps, log *slog.Logger) {
	for _, t := range workflow.AllNodeTypes {
		if !d.Has(t) {
			log.Warn("akış düğümü çalıştırılamaz (yetenek bağlı değil)", "nodeType", string(t))
		}
	}
}
