package skill

import (
	"context"
	"errors"
	"fmt"
	"time"

	"fiko-ai/internal/event"
)

// ToolExecutor bir skill'i bağlı tool'u üzerinden çalıştırır (Faz 4).
// ARAYÜZ olarak tanımlanır çünkü `tool` paketi `skill` paketini bilmemelidir
// (bağımlılık yönü tek yönlü: api → skill → {tool arayüzleri}).
type ToolExecutor interface {
	ExecuteTool(ctx context.Context, toolName string, args map[string]any) (ToolRunResult, error)
}

// ToolRunResult bir tool çalıştırmasının sonucudur.
type ToolRunResult struct {
	ToolName   string         `json:"toolName"`
	Output     map[string]any `json:"output"`
	DurationMs int64          `json:"durationMs"`
}

// ToolChecker tool'un var olup olmadığını sorar (executorConfig.tool doğrulaması).
// Skill, tool paketine BAĞIMLI OLABİLMEZ; yalnızca arayüzü bilir.
type ToolChecker interface {
	HasTool(ctx context.Context, name string) bool
}

var (
	// ErrNotExecutable skill yürütülemez durumda (taslak/kapalı) ya da yürütücüsü tool dışı.
	ErrNotExecutable = errors.New("skill: çalıştırılamaz durumda")
	// ErrNoExecutor tool sistemi bağlı değilken çalıştırma istenirse döner.
	ErrNoExecutor = errors.New("skill: tool sistemi bağlı değil")
	// ErrExecutorUnsupported executorType=llm henüz yürütülemez (Faz 5'te ajan döngüsü).
	ErrExecutorUnsupported = errors.New("skill: bu yürütücü tipi henüz desteklenmiyor")
)

// SetToolExecutor skill yürütmesini bağlar (Faz 4). Çağrılmazsa ExecuteSkill
// ErrNoExecutor döner; kayıt/doğrulama ETKİLENMEZ.
func (s *Service) SetToolExecutor(e ToolExecutor) { s.toolExec = e }

// SetToolChecker tool varlık denetleyicisini bağlar. Verilmezse executorConfig.tool
// alanındaki ad yalnızca BOŞLUK/BIÇIM denetiminden geçer (tool var mı diye bakılmaz).
func (s *Service) SetToolChecker(c ToolChecker) { s.toolCheck = c }

// ToolName döndürür: skill'in executorConfig'inde yazılı tool adı (yoksa "").
func ToolName(sk *Skill) string {
	if sk == nil {
		return ""
	}
	name, _ := sk.ExecutorConfig["tool"].(string)
	return name
}

// checkExecutorTool executorConfig.tool adının gerçek bir tool'a karşılık geldiğini doğrular.
// DENETÇİ YOKSA bu kontrol yapılmaz: sistem tool'suz (ör. bellek içi test) da çalışabilir.
func (s *Service) checkExecutorTool(ctx context.Context, sk *Skill, errs []FieldError) []FieldError {
	if s.toolCheck == nil || sk.ExecutorType != ExecutorTool {
		return errs
	}
	name := ToolName(sk)
	if name == "" {
		return errs // biçim hatası zaten Validate içinde bildirildi
	}
	if !s.toolCheck.HasTool(ctx, name) {
		errs = append(errs, FieldError{
			Field:   "executorConfig",
			Message: fmt.Sprintf("%q diye bir tool yok (Tool Registry'de tanımlı olmalı)", name),
		})
	}
	return errs
}

// ExecutionResult bir skill'in çalıştırılmasının sonucudur (API yanıtı).
type ExecutionResult struct {
	SkillID    string         `json:"skillId"`
	SkillName  string         `json:"skillName"`
	Tool       string         `json:"tool"`
	Output     map[string]any `json:"output"`
	DurationMs int64          `json:"durationMs"`
	At         time.Time      `json:"at"`
}

// ExecuteSkill skill'i bağlı tool'u üzerinden çalıştırır (PROJE.MD: ExecuteSkill).
//
// Ön koşullar ve sıra:
//  1. skill var ve ACTIVE olmalı (taslak/kapalı çalıştırılmaz),
//  2. executorType=tool olmalı (llm yürütücüsü Faz 5),
//  3. tool adı kayıtlı olmalı (denetçi bağlıysa),
//  4. executor yetkiyi kendi içinde denetler (yetki hatası buradan sızmaz).
func (s *Service) ExecuteSkill(ctx context.Context, skillID string, args map[string]any) (*ExecutionResult, error) {
	sk, err := s.repo.Get(ctx, skillID)
	if err != nil {
		return nil, err
	}
	if sk.Status != StatusActive {
		return nil, ErrNotExecutable
	}
	switch sk.ExecutorType {
	case ExecutorTool:
		// devam
	default:
		return nil, fmt.Errorf("%w: %s", ErrExecutorUnsupported, sk.ExecutorType)
	}
	name := ToolName(sk)
	if name == "" {
		return nil, fmt.Errorf("%w: executorConfig.tool boş", ErrExecutorUnsupported)
	}
	if s.toolCheck != nil && !s.toolCheck.HasTool(ctx, name) {
		return nil, ErrNotFound
	}
	if s.toolExec == nil {
		return nil, ErrNoExecutor
	}

	if errs := validateArguments(sk, args); len(errs) > 0 {
		return nil, &ValidationError{Errors: errs}
	}

	run, err := s.toolExec.ExecuteTool(ctx, name, args)
	if err != nil {
		s.publishExec(event.SkillExecuteFailed, sk, name)
		return nil, err
	}
	res := &ExecutionResult{
		SkillID: sk.ID, SkillName: sk.Name, Tool: name,
		Output: run.Output, DurationMs: run.DurationMs, At: s.now(),
	}
	s.publishExec(event.SkillExecuted, sk, name)
	return res, nil
}

// validateArguments skill parametrelerine göre çağrı argümanlarını kontrol eder.
// Tool'un kendi şeması da doğrulayacak; bu katman LLM'in ürettiği argümanlarda
// "zorunlu alan unutuldu" hatasını daha erken ve anlaşılır biçimde yakalar.
func validateArguments(sk *Skill, args map[string]any) []FieldError {
	var errs []FieldError
	for i, p := range sk.Parameters {
		if !p.Required {
			continue
		}
		if v, ok := args[p.Name]; !ok || v == nil || v == "" {
			errs = append(errs, FieldError{
				Field:   fmt.Sprintf("parameters[%d]", i),
				Message: fmt.Sprintf("%q zorunlu", p.Name),
			})
		}
	}
	return errs
}

func (s *Service) publishExec(t event.Type, sk *Skill, toolName string) {
	if s.bus == nil {
		return
	}
	s.bus.Publish(context.Background(), event.Event{
		Type: t,
		Data: map[string]any{"id": sk.ID, "name": sk.Name, "tool": toolName},
	})
}
