package main

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

	"fiko-ai/config"
	"fiko-ai/internal/agent"
	"fiko-ai/internal/event"
	"fiko-ai/internal/llm"
	"fiko-ai/internal/skill"
	"fiko-ai/internal/tool"
	"fiko-ai/internal/tool/builtin"
)

// toolStack Tool Registry + Executor + izin deposudur (Faz 4).
// Skill, tool paketini BİLMEZ; arayüzler (skill.ToolExecutor / skill.ToolChecker)
// burada, DI noktasında gerçek uygulamalara bağlanır.
type toolStack struct {
	registry *tool.Registry
	executor *tool.Executor
	grants   *tool.GrantStore
}

// setupTools Tool Registry'yi kurar, builtin tool'ları kaydeder ve yetki deposunu oluşturur.
//
// Güvenlik notları:
//   - Varsayılan politika yalnızca EXECUTE'tur; high/critical risk kapıları kapalıdır.
//   - sys.exec yalnızca TOOL_EXEC_ENABLED=true VE izin listesi boş değilse tanımlanır.
//   - Builtin tool'ların kullanıcı düzenlemesi açılışta EZİLMEZ (EnsureBuiltin kuralı).
func setupTools(ctx context.Context, repo tool.Repository, bus event.Bus, cfg config.Config,
	rec tool.Recorder, log *slog.Logger) (*toolStack, error) {

	grants := tool.NewGrantStore(defaultPolicy(cfg.Tools))

	defPerm, err := tool.PermissionsOf(cfg.Tools.DefaultPermissions)
	if err != nil {
		return nil, fmt.Errorf("config: TOOL_DEFAULT_PERMISSIONS geçersiz: %w", err)
	}
	grants.SetPolicy("", tool.Policy{
		Grants: defPerm, AllowHighRisk: cfg.Tools.AllowHighRisk, AllowCritical: cfg.Tools.AllowCritical,
	})

	reg := tool.NewRegistry(repo, bus)
	// Uzak (kind=http) tool politikası: genel kapı + SSRF koruması.
	// AllowPrivate=false iken loopback/özel ağ/cloud metadata HEDEFLENEMEZ.
	reg.SetHTTPPolicy(tool.HTTPPolicy{
		Enabled:      cfg.Tools.HTTPEnabled,
		AllowPrivate: cfg.Tools.HTTPAllowPrivate,
		MaxBytes:     cfg.Tools.HTTPMaxBytes,
		MaxRedirects: cfg.Tools.HTTPMaxRedirects,
		Timeout:      cfg.Tools.HTTPTimeout,
	})
	if cfg.Tools.HTTPEnabled {
		log.Info("uzak (http) tool çalıştırma açık",
			"özelAğ", cfg.Tools.HTTPAllowPrivate, "maksBayt", cfg.Tools.HTTPMaxBytes)
	} else {
		log.Info("uzak (http) tool çalıştırma KAPALI (TOOL_HTTP_ENABLED=false)")
	}
	exec := tool.NewExecutor(reg, grants, rec, bus, tool.ExecutorOptions{
		MaxWait: cfg.Tools.MaxWait, MaxAttempts: cfg.Tools.MaxAttempts, Logger: log,
	})

	opts := builtin.Options{
		FSRoot:        cfg.Tools.FSRoot,
		MaxReadBytes:  cfg.Tools.MaxReadBytes,
		MaxWriteBytes: cfg.Tools.MaxWriteBytes,
		ExecTimeout:   cfg.Tools.ExecTimeout,
		Logger:        log, // Register uyarıları (kapalı araç, senkron hatası) sunucu log'una düşer
	}
	if cfg.Tools.ExecEnabled {
		opts.ExecAllowed = cfg.Tools.ExecAllowed
		log.Warn("süreç çalıştırma tool'u ETKİN: yalnızca izin listesindeki programlar çalışabilir",
			"izinliProgram", cfg.Tools.ExecAllowed)
	} else if len(cfg.Tools.ExecAllowed) > 0 {
		log.Warn("TOOL_EXEC_ALLOW dolu ama TOOL_EXEC_ENABLED=false: sys.exec tanımlanmayacak")
	}

	// İnternet tool'ları: yalnızca uzak çağrı genel kapısı açıksa ve SSRF korumalı istemciyle.
	// NOT: web.search'ın sağlayıcısı YERELDE çalışan SearXNG ise (örn. http://127.0.0.1:8181)
	// istemci bu adrese SABİTLER; tüm web tool'larına özel ağ erişimi açılmaz.
	if cfg.Tools.WebEnabled && cfg.Tools.HTTPEnabled {
		opts.WebEnabled = true
		opts.HTTPClient = tool.NewSafeClient(tool.HTTPPolicy{
			AllowPrivate: cfg.Tools.HTTPAllowPrivate,
			MaxRedirects: 5,
			Timeout:      cfg.Tools.HTTPTimeout,
		})
		opts.WebSearchProvider = cfg.Tools.WebSearchProvider
		opts.WebSearchURL = cfg.Tools.WebSearchURL
		opts.WebSearchKey = cfg.Tools.WebSearchKey
		// Yerel arama motoru: yalnızca yapılandırılan host'a bağlanabilen istemci.
		if p := strings.ToLower(strings.TrimSpace(cfg.Tools.WebSearchProvider)); p == "searxng" {
			if host := tool.PinnedHost(cfg.Tools.WebSearchURL); host != "" {
				opts.WebSearchClient = tool.NewPinnedClient([]string{host}, tool.HTTPPolicy{
					MaxRedirects: 2,
					Timeout:      cfg.Tools.HTTPTimeout,
				})
				log.Info("arama sağlayıcısı: yerel SearXNG (sabitlenmiş adres)", "adres", host)
			} else {
				log.Warn("WEB_SEARCH_PROVIDER=searxng ama WEB_SEARCH_URL bozuk/geçersiz; web.search çalışmayacak")
			}
		}
		log.Info("internet tool'ları açık (web.fetch, web.search, web.exchange_rate)",
			"aramaSağlayıcı", cfg.Tools.WebSearchProvider)
	} else if cfg.Tools.WebEnabled {
		log.Warn("WEB_ENABLED=true ama TOOL_HTTP_ENABLED=false: internet tool'ları tanımlanmayacak")
	}
	if cfg.Tools.BizEnabled {
		opts.BizEnabled = true
		opts.BizPath = cfg.Tools.BizPath
		log.Info("şirket yönetimi tool'ları açık (biz.*)", "veri", cfg.Tools.BizPath)
	}

	if err := builtin.Register(ctx, reg, opts); err != nil {
		return nil, fmt.Errorf("builtin tool kaydı başarısız: %w", err)
	}
	if err := os.MkdirAll(cfg.Tools.FSRoot, 0o755); err != nil {
		log.Warn("tool dosya kökü oluşturulamadı", "path", cfg.Tools.FSRoot, "err", err)
	}
	log.Info("tool sistemi hazır",
		"kok", cfg.Tools.FSRoot, "varsayilanIzin", defPerm.String(),
		"highRisk", cfg.Tools.AllowHighRisk, "criticalRisk", cfg.Tools.AllowCritical)
	return &toolStack{registry: reg, executor: exec, grants: grants}, nil
}

// defaultPolicy izin listesi boşsa güvenli varsayılana düşer.
func defaultPolicy(t config.Tools) tool.Policy {
	if len(t.DefaultPermissions) == 0 {
		return tool.DefaultPolicy()
	}
	p, err := tool.PermissionsOf(t.DefaultPermissions)
	if err != nil {
		return tool.DefaultPolicy()
	}
	return tool.Policy{Grants: p, AllowHighRisk: t.AllowHighRisk, AllowCritical: t.AllowCritical}
}

// skillRetriever, skill.Service.Search'i agent.SkillRetriever arayüzüne uyarlar (Faz 5c).
// Hata veya boş sonuç DÖNÜŞÜM DEĞİLDİR: ajan o turda tüm izinli tool'ları sunar.
// Retrieval bir öneridir; üretimi asla engellememelidir.
type skillRetriever struct{ svc *skill.Service }

func (s skillRetriever) TopSkills(ctx context.Context, query string, limit int) []agent.RetrievedSkill {
	if s.svc == nil || strings.TrimSpace(query) == "" {
		return nil
	}
	// Hybrid mod: embedding varsa semantik ağırlıklı, yoksa anahtar kelimeye düşer (200 döner).
	res, err := s.svc.Search(ctx, skill.SearchOptions{Query: query, Mode: skill.ModeHybrid, Limit: limit})
	if err != nil {
		return nil
	}
	out := make([]agent.RetrievedSkill, 0, len(res))
	for _, r := range res {
		out = append(out, agent.RetrievedSkill{
			Name:        r.Skill.Name,
			Description: r.Skill.Description,
			Tool:        toolOf(r.Skill),
			Score:       r.Score,
		})
	}
	return out
}

// toolOf, skill'in bağlı olduğu tool adını döndürür (executorType=tool ise); yoksa boş.
func toolOf(sk skill.Skill) string {
	if sk.ExecutorType != skill.ExecutorTool {
		return ""
	}
	name, _ := sk.ExecutorConfig["tool"].(string)
	return strings.TrimSpace(name)
}

// bindSkillTools skill servisini tool sistemine bağlar: executorConfig.tool varlığı
// denetlenir ve ExecuteSkill gerçekten tool çalıştırabilir.
func bindSkillTools(svc *skill.Service, st *toolStack) {
	svc.SetToolChecker(toolChecker{st.registry})
	svc.SetToolExecutor(toolRunner{st.executor})
}

// agentRunner tool.Executor'u agent.ToolRunner arayüzüne uyarlar.
// NOT: yetki denetimi Executor'ın İÇİNDEDİR; burada yalnızca bağlanır.
type agentRunner struct{ exec *tool.Executor }

func (r agentRunner) Run(ctx context.Context, name string, args map[string]any, userID string, progress func(string, int)) (agent.ToolResult, error) {
	res, err := r.exec.Execute(ctx, tool.Request{
		Ref: name, Input: args, Actor: "agent", UserID: userID,
		Progress: progress,
	})
	if err != nil {
		return agent.ToolResult{}, err
	}
	return agent.ToolResult{
		Output:     res.Output,
		DurationMs: res.DurationMs,
		UI:         string(res.UI), // arayüz sonucu doğru görünümde göstersin
	}, nil
}

// agentCatalog, modele sunulacak tool listesini üretir.
//
// KURAL: yalnızca ETKİN + ÇALIŞTIRILABİLİR + kullanıcıya AÇIK tool'lar sunulur.
// Modelin çağırdığı ama yetkisi olmayan bir tool hata olarak döner (403) ve döngü bozulur;
// daha iyisi modele hiç sunmamaktır — "reddedilen tool çağrısı" döngüsü bozar.
type agentCatalog struct {
	registry *tool.Registry
	grants   *tool.GrantStore
}

func (c agentCatalog) Available(ctx context.Context, userID string) []llm.ToolSpec {
	items, err := c.registry.List(ctx, tool.ListFilter{Status: tool.StatusEnabled}, 0, 0)
	if err != nil {
		return nil
	}
	policy := c.grants.PolicyFor(userID)
	out := make([]llm.ToolSpec, 0, len(items))
	for i := range items {
		t := items[i]
		if !c.registry.ExecutableTool(&t) {
			continue
		}
		if !policy.Allows(&t) {
			continue
		}
		out = append(out, llm.ToolSpec{
			Name:        t.Name,
			Description: t.Description,
			InputSchema: modelSchema(t),
		})
	}
	return out
}

// modelSchema, modele gönderilen şemadan AJAN ALANLARINI gizler.
//
// Neden? (Canlı hata 04.10.2026) `confirmed` alanı biz.* araçlarının şemasında duruyordu.
// Küçük bir model bu alanı görüp kendi başına `confirmed:true` gönderiyor, hatta
// "Onay gönderdim" diye yorumlayabiliyordu. Güvenlik zaten modele güvenmemeye dayanıyor
// (bkz. agent.agentConfirmedTool) ama modele İMKÂNSIZ olanı imkânsızlaştırmak daha sağlamdır:
// alan gizlenir, değeri yalnızca ajan/kullanıcı onayı ekleyebilir (agent/confirm.go).
func modelSchema(t tool.Tool) map[string]any {
	if !strings.HasPrefix(t.Name, "biz.") || len(t.InputSchema) == 0 {
		return t.InputSchema
	}
	props, _ := t.InputSchema["properties"].(map[string]any)
	if _, ok := props["confirmed"]; !ok {
		return t.InputSchema // gizlenecek alan yok
	}
	// KOPYALA: kayıttaki (Mongo'daki) şema mutasyona uğramamalı.
	out := make(map[string]any, len(t.InputSchema))
	for k, v := range t.InputSchema {
		out[k] = v
	}
	cleanProps := make(map[string]any, len(props))
	for k, v := range props {
		if k != "confirmed" {
			cleanProps[k] = v
		}
	}
	out["properties"] = cleanProps
	// required listesinde de varsa çıkar (yoksa model görmese de şema tutarsız olur).
	if req, ok := out["required"].([]any); ok {
		keep := make([]any, 0, len(req))
		for _, r := range req {
			if s, _ := r.(string); s != "confirmed" {
				keep = append(keep, r)
			}
		}
		out["required"] = keep
	}
	return out
}

// toolChecker Registry'yi skill.ToolChecker arayüzüne uyarlar.
type toolChecker struct{ reg *tool.Registry }

func (c toolChecker) HasTool(ctx context.Context, name string) bool {
	_, err := c.reg.GetByName(ctx, name)
	return err == nil
}

// toolRunner Executor'u skill.ToolExecutor arayüzüne uyarlar.
// Not: yetki bu noktada ADININ aidiyetinde denetlenir (tool.Request.UserID boş → varsayılan politika).
// Faz 5'te ajan, gerçek kullanıcı kimliğiyle çağıracak.
type toolRunner struct{ exec *tool.Executor }

func (r toolRunner) ExecuteTool(ctx context.Context, name string, args map[string]any) (skill.ToolRunResult, error) {
	res, err := r.exec.Execute(ctx, tool.Request{Ref: name, Input: args, Actor: "agent"})
	if err != nil {
		return skill.ToolRunResult{}, err
	}
	return skill.ToolRunResult{ToolName: res.ToolName, Output: res.Output, DurationMs: res.DurationMs}, nil
}
