package tool

import (
	"context"
	"errors"
	"log/slog"
	"reflect"
	"slices"
	"strings"
	"sync"
	"time"

	"fiko-ai/internal/event"
	"fiko-ai/pkg/id"
)

// Registry Tool Registry'nin iş katmanıdır: kayıt/güncelleme/silme/listeleme kuralları
// ve çalışma zamanı handler bağlama. Depolama Repository'dedir.
//
// İki kayıt türü vardır:
//   - builtin : kodla gelir, açılışta EnsureBuiltin ile kaydedilir, handler'ı bağlıdır,
//     silinemez (yalnızca kapatılır).
//   - http    : uzak servisi çağırır; handler'ı çalışma zamanında SSRF korumalı üretilir.
//   - custom  : kullanıcı arayüzünden eklenir; handler'ı yoktur, YÜRÜTÜLEMEZ
//     (Execute ErrNotExecutable döner).
//
// Yalnızca kalıcı olan metadata burada yazılır; Handler bellek içindedir (Mongo'ya yazılamaz).
type Registry struct {
	mu         sync.RWMutex
	repo       Repository
	bus        event.Bus // nil olabilir
	handlers   map[string]Handler
	httpPolicy HTTPPolicy
	log        *slog.Logger
	now        func() time.Time
}

// NewRegistry Registry kurar. bus nil olabilir (olay yayınlanmaz).
func NewRegistry(repo Repository, bus event.Bus) *Registry {
	return &Registry{
		repo:     repo,
		bus:      bus,
		handlers: make(map[string]Handler),
		log:      slog.Default(),
		now:      func() time.Time { return time.Now().UTC() },
	}
}

// Register yeni tool tanımı ekler. Kimlik ve zaman damgaları sunucuya aittir.
// Geçersizse *ValidationError, ad çakışırsa ErrDuplicateName döner.
// Yalnızca metadata yazılır; handler bağlanmaz (bkz. Bind).
func (r *Registry) Register(ctx context.Context, in Tool) (*Tool, error) {
	t := in.Clone()
	t.ID = id.New("tool")
	Normalize(&t)
	if errs := Validate(&t); len(errs) > 0 {
		return nil, &ValidationError{Errors: errs}
	}
	now := r.now()
	t.CreatedAt, t.UpdatedAt = now, now
	if err := r.repo.Create(ctx, &t); err != nil {
		return nil, err
	}
	r.publish(event.ToolRegistered, &t)
	return &t, nil
}

// Update tool'u verilen alanlarla TAMAMEN değiştirir (kimlik ve oluşturma zamanı korunur).
// Builtin tool'lar buradan da güncellenebilir (açıklama/şema/timeout düzeltmesi);
// yalnızca silinmesi yasaktır.
func (r *Registry) Update(ctx context.Context, toolID string, in Tool) (*Tool, error) {
	existing, err := r.repo.Get(ctx, toolID)
	if err != nil {
		return nil, err
	}
	t := in.Clone()
	t.ID = existing.ID
	t.CreatedAt = existing.CreatedAt
	t.Builtin = existing.Builtin // bu alan kullanıcı gövdesinden gelmez
	Normalize(&t)
	if errs := Validate(&t); len(errs) > 0 {
		return nil, &ValidationError{Errors: errs}
	}
	t.UpdatedAt = r.now()
	if err := r.repo.Update(ctx, &t); err != nil {
		return nil, err
	}
	r.publish(event.ToolUpdated, &t)
	return &t, nil
}

// SetStatus tool'u açar/kapatır. Kurallar yeniden denetlenir: kapatılan tool
// silinmez, yalnızca yürütülemez (Execute ErrDisabled döner).
func (r *Registry) SetStatus(ctx context.Context, toolID string, st Status) (*Tool, error) {
	t, err := r.repo.Get(ctx, toolID)
	if err != nil {
		return nil, err
	}
	if !st.Valid() {
		return nil, &ValidationError{Errors: []FieldError{{Field: "status", Message: "enabled veya disabled olmalı"}}}
	}
	t.Status = st
	t.UpdatedAt = r.now()
	if err := r.repo.Update(ctx, t); err != nil {
		return nil, err
	}
	r.publish(event.ToolUpdated, t)
	return t, nil
}

// Delete tool'u siler. Builtin tool'lar SİLİNEMEZ (ErrBuiltin): kodla gelen kaydın
// silinmesi bir sonraki açılışta sessizce geri gelirdi. Bunun yerine kapatılır.
func (r *Registry) Delete(ctx context.Context, toolID string) error {
	existing, err := r.repo.Get(ctx, toolID)
	if err != nil {
		return err
	}
	if existing.Builtin {
		return ErrBuiltin
	}
	if err := r.repo.Delete(ctx, toolID); err != nil {
		return err
	}
	r.publish(event.ToolDeleted, existing)
	return nil
}

// Get tek tool döndürür; yoksa ErrNotFound.
func (r *Registry) Get(ctx context.Context, toolID string) (*Tool, error) {
	return r.repo.Get(ctx, toolID)
}

// GetByName adla arar; yoksa ErrNotFound.
func (r *Registry) GetByName(ctx context.Context, name string) (*Tool, error) {
	return r.repo.GetByName(ctx, name)
}

// List filtreye uyan tool'ları ada göre sıralı döndürür.
func (r *Registry) List(ctx context.Context, f ListFilter, limit, offset int) ([]Tool, error) {
	return r.repo.List(ctx, f, limit, offset)
}

// Validate kaydetmeden doğrular ve TÜM hataları döndürür (arayüzün canlı doğrulaması).
// toolID doluysa (güncelleme) o tool'un kendi adı çakışma sayılmaz.
func (r *Registry) Validate(ctx context.Context, toolID string, in Tool) ([]FieldError, error) {
	t := in.Clone()
	Normalize(&t)
	errs := Validate(&t)
	if t.Name != "" {
		other, err := r.repo.GetByName(ctx, t.Name)
		switch {
		case errors.Is(err, ErrNotFound):
		case err != nil:
			return nil, err
		case other.ID != toolID:
			errs = append(errs, FieldError{Field: "name", Message: "bu ada sahip bir tool zaten var"})
		}
	}
	if errs == nil {
		errs = []FieldError{}
	}
	return errs, nil
}

// Bind çalışma zamanı uygulamasını tool adına bağlar. Aynı ad için tekrar bağlama
// serbesttir (açılışta her zaman bağlanır — kod güncellemesi yeni handler'ı getirir).
func (r *Registry) Bind(name string, h Handler) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.handlers[strings.TrimSpace(name)] = h
}

// Executable adın bağlı bir uygulaması var mı? (listede "executable" alanı için)
func (r *Registry) Executable(name string) bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	_, ok := r.handlers[name]
	return ok
}

// ExecutableTool, verilen tool gerçekten çalıştırılabilir mi?
//
// Neden ad değil de tool? Çünkü HTTP tool'ların uygulaması çalışma zamanında ÜRETİLİR
// (Resolve); yalnız "bağlı handler var mı" diye bakmak onları yanlışlıkla
// "çalıştırılamaz" gösterirdi (18. oturumda canlı testte bu oldu).
func (r *Registry) ExecutableTool(t *Tool) bool {
	if t == nil {
		return false
	}
	if r.Executable(t.Name) {
		return true
	}
	r.mu.RLock()
	policy := r.httpPolicy
	r.mu.RUnlock()
	return t.Kind == KindHTTP && policy.Enabled
}

// Resolve tool'u AD ya da KİMLİK ile bulur ve uygulamasıyla birlikte döndürür.
// Bulunamazsa ErrNotFound, uygulaması yoksa ErrNotExecutable.
//
// HTTP tool'lar (kind=http) bağlı bir Go handler taşımaz; uygulaması burada SSRF
// korumalı istemciyle ÜRETİLİR. Böylece uzak tool eklemek için kod yazmak gerekmez,
// tanım yeterlidir (Faz 4b′).
func (r *Registry) Resolve(ctx context.Context, ref string) (*Tool, Handler, error) {
	ref = strings.TrimSpace(ref)
	if ref == "" {
		return nil, nil, ErrNotFound
	}
	t, err := r.repo.GetByName(ctx, ref)
	if err != nil {
		if !errors.Is(err, ErrNotFound) {
			return nil, nil, err
		}
		if t, err = r.repo.Get(ctx, ref); err != nil {
			return nil, nil, err // ErrNotFound
		}
	}
	r.mu.RLock()
	h, ok := r.handlers[t.Name]
	policy := r.httpPolicy
	r.mu.RUnlock()
	if ok {
		return t, h, nil
	}
	if t.Kind == KindHTTP {
		if !policy.Enabled {
			return nil, nil, ErrHTTPDisabled
		}
		h, err := NewHTTPHandler(t, policy)
		if err != nil {
			return nil, nil, err
		}
		return t, h, nil
	}
	return nil, nil, ErrNotExecutable
}

// SetHTTPPolicy uzak (kind=http) tool'ların çalışma politikasını ayarlar (açılışta bir kez).
func (r *Registry) SetHTTPPolicy(p HTTPPolicy) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.httpPolicy = p
}

// EnsureBuiltin kodla gelen tool'u açılışta hazırlar. Kurallar:
//   - kayıt yoksa oluşturulur (created=true döner),
//   - varsa TANIMI EZİLMEZ (kullanıcı düzenlemiş olabilir), yalnızca handler bağlanır,
//   - enabled=false ise kayıt kapatılır (örn. TOOL_SYS_EXEC_ENABLED kapalıyken);
//     kapalı bir tool hiçbir koşulda otomatik AÇILMAZ — kullanıcı kararı sayılır.
func (r *Registry) EnsureBuiltin(ctx context.Context, def Tool, h Handler, enabled bool) (*Tool, bool, error) {
	def = def.Clone()
	def.Builtin = true
	def.Kind = KindBuiltin
	Normalize(&def)
	if errs := Validate(&def); len(errs) > 0 {
		return nil, false, &ValidationError{Errors: errs}
	}
	if h != nil {
		r.Bind(def.Name, h)
	}

	existing, err := r.repo.GetByName(ctx, def.Name)
	switch {
	case errors.Is(err, ErrNotFound):
		def.ID = id.New("tool")
		if !enabled {
			def.Status = StatusDisabled
		}
		now := r.now()
		def.CreatedAt, def.UpdatedAt = now, now
		if err := r.repo.Create(ctx, &def); err != nil {
			return nil, false, err
		}
		r.publish(event.ToolRegistered, &def)
		return &def, true, nil
	case err != nil:
		return nil, false, err
	}

	if !enabled && existing.Status == StatusEnabled {
		existing.Status = StatusDisabled
		existing.UpdatedAt = r.now()
		if err := r.repo.Update(ctx, existing); err != nil {
			return nil, false, err
		}
		r.publish(event.ToolUpdated, existing)
		return existing, false, nil
	}
	return existing, false, nil
}

// SyncBuiltinFields, var olan bir builtin tool kaydının KOD TÜRETTİĞİ alanlarını tazeler.
// Kullanıcı alanlarına (status, tags, config) DOKUNMAZ: kullanıcının tercihi korunur.
//
// Neden var? (Canlı hata) Bellek tool'ları ilk sürümde "write" izniyle kaydedilmişti.
// EnsureBuiltin mevcut kaydı güncellemediği için izin eski kaldı; varsayılan politika
// write içermediğinden bu tool'lar modele HİÇ SUNULMADI ve bellek hiç çalışmadı.
// Kodu düzeltmek tek başına yetmiyordu — kayıttaki alan da tazelenmeliydi.
//
// Güvenlik: yalnızca BUILTIN tool'lar için çalışır; kullanıcının oluşturduğu tool'ların
// tanımına dokunmak onları sessizce değiştirirdi.
func (r *Registry) SyncBuiltinFields(ctx context.Context, def Tool) error {
	def = def.Clone()
	def.Builtin = true
	def.Kind = KindBuiltin
	Normalize(&def)
	if errs := Validate(&def); len(errs) > 0 {
		return &ValidationError{Errors: errs}
	}
	existing, err := r.repo.GetByName(ctx, def.Name)
	if errors.Is(err, ErrNotFound) {
		return nil // EnsureBuiltin zaten oluşturdu; kayıt yoksa bir şey yapmaya gerek yok
	}
	if err != nil {
		return err
	}
	// Yalnızca alan değiştiyse yaz (gereksiz yazma olay üretmesin).
	if slices.Equal(existing.Permissions, def.Permissions) &&
		existing.Risk == def.Risk &&
		existing.Version == def.Version &&
		existing.Title == def.Title &&
		existing.Description == def.Description &&
		existing.TimeoutMs == def.TimeoutMs &&
		existing.UI == def.UI &&
		reflect.DeepEqual(existing.InputSchema, def.InputSchema) &&
		reflect.DeepEqual(existing.OutputSchema, def.OutputSchema) {
		return nil
	}
	existing.Title = def.Title
	existing.Description = def.Description
	existing.Version = def.Version
	existing.Kind = def.Kind
	existing.InputSchema = def.InputSchema
	existing.OutputSchema = def.OutputSchema
	existing.Permissions = def.Permissions
	existing.Risk = def.Risk
	existing.TimeoutMs = def.TimeoutMs
	existing.UI = def.UI
	existing.Builtin = true
	existing.UpdatedAt = r.now()
	if err := r.repo.Update(ctx, existing); err != nil {
		return err
	}
	r.publish(event.ToolUpdated, existing)
	return nil
}

func (r *Registry) publish(t event.Type, tl *Tool) {
	if r.bus == nil {
		return
	}
	r.bus.Publish(context.Background(), event.Event{
		Type: t,
		Data: map[string]any{"id": tl.ID, "name": tl.Name},
	})
}
