package workflow

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"sync"
	"time"

	"fiko-ai/pkg/id"
)

// Varsayılan servis sınırları.
const (
	DefaultMaxConcurrent = 4
	DefaultRunTimeout    = 5 * time.Minute
	// MaxInputRunes bir yürütmenin girdi metninin üst sınırıdır.
	MaxInputRunes = 20000
	// persistTimeout tek bir yürütme kaydı yazımının üst sınırı: yavaş veritabanı akışı sonsuza dek tutmasın.
	persistTimeout = 5 * time.Second
)

// Options servis ayarlarıdır; sıfır değerler varsayılanla değiştirilir.
type Options struct {
	Logger        *slog.Logger
	MaxConcurrent int           // aynı anda en çok kaç yürütme
	RunTimeout    time.Duration // tek yürütmenin üst süresi
	// OnFinish bir yürütme bitince (başarılı, hatalı ya da iptal) çağrılır; denetim kaydı için.
	// Yürütme goroutine'inde senkron çalışır: ağır iş yapmamalıdır.
	OnFinish func(Execution)
}

// Service akış iş kurallarını ve yürütmelerin yaşam döngüsünü yönetir:
// CRUD (sahiplik, doğrulama), arka planda çalıştırma, iptal, kapanış.
type Service struct {
	store Store
	deps  Deps
	opts  Options
	log   *slog.Logger
	now   func() time.Time

	baseCtx context.Context // servis ömrü; Close iptal eder
	stop    context.CancelFunc

	mu      sync.Mutex
	running map[string]*runInfo
	closed  bool
	wg      sync.WaitGroup
}

type runInfo struct {
	cancel     context.CancelFunc
	userID     string
	workflowID string
}

// NewService servisi kurar. deps'te nil olan yetenekler o node türlerini çalıştırılamaz yapar.
func NewService(store Store, deps Deps, opts Options) *Service {
	if opts.Logger == nil {
		opts.Logger = slog.Default()
	}
	if opts.MaxConcurrent < 1 {
		opts.MaxConcurrent = DefaultMaxConcurrent
	}
	if opts.RunTimeout <= 0 {
		opts.RunTimeout = DefaultRunTimeout
	}
	ctx, stop := context.WithCancel(context.Background())
	return &Service{
		store: store, deps: deps, opts: opts, log: opts.Logger,
		now:     func() time.Time { return time.Now().UTC() },
		baseCtx: ctx, stop: stop,
		running: map[string]*runInfo{},
	}
}

// ---- Akış CRUD ----

// Create akışı kaydeder. Kimlik, sahip ve zaman damgaları sunucuya aittir (girdidekiler yok sayılır).
// Kayıt ESNEKTİR: yapısal olarak geçerli taslak (ör. output'suz) kaydedilebilir; çalıştırma katıdır.
func (s *Service) Create(ctx context.Context, userID string, in Workflow) (*Workflow, error) {
	w := in.Clone()
	w.ID, w.UserID = id.New("wf"), userID
	w.CreatedAt = s.now()
	w.UpdatedAt = w.CreatedAt
	Normalize(&w)
	if errs := Validate(&w); len(errs) > 0 {
		return nil, &ValidationError{Errors: errs}
	}
	if err := s.store.Create(ctx, &w); err != nil {
		return nil, err
	}
	return &w, nil
}

// Update akışı gövdedeki alanlarla TAMAMEN değiştirir; kimlik, sahip ve oluşturulma zamanı korunur.
func (s *Service) Update(ctx context.Context, userID, wfID string, in Workflow) (*Workflow, error) {
	cur, err := s.store.Get(ctx, userID, wfID)
	if err != nil {
		return nil, err
	}
	w := in.Clone()
	w.ID, w.UserID, w.CreatedAt = cur.ID, cur.UserID, cur.CreatedAt
	w.UpdatedAt = s.now()
	Normalize(&w)
	if errs := Validate(&w); len(errs) > 0 {
		return nil, &ValidationError{Errors: errs}
	}
	if err := s.store.Update(ctx, &w); err != nil {
		return nil, err
	}
	return &w, nil
}

func (s *Service) Get(ctx context.Context, userID, wfID string) (*Workflow, error) {
	return s.store.Get(ctx, userID, wfID)
}

func (s *Service) List(ctx context.Context, userID string, f ListFilter, limit, offset int) ([]Workflow, error) {
	return s.store.List(ctx, userID, f, limit, offset)
}

// Delete akışı ve yürütme geçmişini siler; akışın süren yürütmeleri önce iptal edilir.
func (s *Service) Delete(ctx context.Context, userID, wfID string) error {
	if err := s.store.Delete(ctx, userID, wfID); err != nil {
		return err
	}
	s.mu.Lock()
	for _, ri := range s.running {
		if ri.userID == userID && ri.workflowID == wfID {
			ri.cancel()
		}
	}
	s.mu.Unlock()
	if _, err := s.store.DeleteExecutionsByWorkflow(ctx, userID, wfID); err != nil {
		s.log.Warn("silinen akışın yürütme geçmişi temizlenemedi", "workflow", wfID, "err", err)
	}
	return nil
}

// Duplicate "<ad> (kopya)" adıyla yeni bir kopya oluşturur.
func (s *Service) Duplicate(ctx context.Context, userID, wfID string) (*Workflow, error) {
	cur, err := s.store.Get(ctx, userID, wfID)
	if err != nil {
		return nil, err
	}
	const suffix = " (kopya)"
	name := cur.Name
	if r := []rune(name); len(r) > MaxNameRunes-len([]rune(suffix)) {
		name = string(r[:MaxNameRunes-len([]rune(suffix))])
	}
	cp := cur.Clone()
	cp.Name = name + suffix
	return s.Create(ctx, userID, cp)
}

// Inspection bir akışın kaydedilebilirlik ve çalıştırılabilirlik raporudur (arayüzün canlı doğrulaması).
type Inspection struct {
	Errors    []FieldError // yapısal hatalar: kaydı engeller
	Runnable  bool         // çalıştırılabilir mi?
	RunErrors []FieldError // çalıştırmayı engelleyen hatalar (kaydı engellemez)
}

// Inspect akışı kaydetmeden denetler. Geçersizlik HATA değil sonuçtur.
func (s *Service) Inspect(in Workflow) Inspection {
	w := in.Clone()
	Normalize(&w)
	out := Inspection{Errors: []FieldError{}, RunErrors: []FieldError{}}
	if errs := Validate(&w); len(errs) > 0 {
		out.Errors = errs
		return out
	}
	out.RunErrors = append(out.RunErrors, Check(&w)...)
	if missing := s.deps.Missing(&w); len(missing) > 0 {
		out.RunErrors = append(out.RunErrors, FieldError{
			Field: "nodes", Message: (&CapabilityError{Types: missing}).Error(),
		})
	}
	out.Runnable = len(out.RunErrors) == 0
	return out
}

// Meta arayüzün paleti için sunucunun yeteneklerini ve sınırlarını bildirir.
type Meta struct {
	// Available her node türü için çalıştırılabilir mi (ör. LLM bağlı değilse llm=false).
	Available map[NodeType]bool `json:"available"`
	// Ops condition node'unun operatörleri.
	Ops []string `json:"ops"`
	// Reserved şablonlarda ayrılmış değişken adları (node kimliği olamaz).
	Reserved []string   `json:"reserved"`
	Limits   MetaLimits `json:"limits"`
}

// MetaLimits arayüzün de bildiği sınırlardır.
type MetaLimits struct {
	MaxNodes          int `json:"maxNodes"`
	MaxEdges          int `json:"maxEdges"`
	MaxSteps          int `json:"maxSteps"`
	MaxLoopIterations int `json:"maxLoopIterations"`
	MaxInputRunes     int `json:"maxInputRunes"`
	RunTimeoutSeconds int `json:"runTimeoutSeconds"`
	MaxConcurrent     int `json:"maxConcurrent"`
}

func (s *Service) Meta() Meta {
	avail := make(map[NodeType]bool, len(AllNodeTypes))
	for _, t := range AllNodeTypes {
		avail[t] = s.deps.Has(t)
	}
	return Meta{
		Available: avail,
		Ops:       append([]string{}, conditionOps...),
		Reserved:  append([]string{}, reservedVars...),
		Limits: MetaLimits{
			MaxNodes: MaxNodes, MaxEdges: MaxEdges, MaxSteps: MaxSteps,
			MaxLoopIterations: MaxLoopIterations, MaxInputRunes: MaxInputRunes,
			RunTimeoutSeconds: int(s.opts.RunTimeout / time.Second), MaxConcurrent: s.opts.MaxConcurrent,
		},
	}
}

// ---- Yürütme ----

// StartInput bir yürütmenin girdileridir.
type StartInput struct {
	Input          string
	ConversationID string // isteğe bağlı: memory node'u bu konuşmanın geçmişini okur
}

// Start akışı ARKA PLANDA çalıştırmaya başlar ve "running" durumdaki kaydı hemen döndürür.
// İlerleme GetExecution ile izlenir. Çalıştırılamaz akış (Check hatası) ya da bağlı olmayan yetenek
// gerektiren akış başlamaz: *ValidationError{NotRunnable} / *CapabilityError döner.
func (s *Service) Start(ctx context.Context, userID, wfID string, in StartInput) (*Execution, error) {
	if runes(in.Input) > MaxInputRunes {
		return nil, &ValidationError{NotRunnable: false, Errors: []FieldError{
			{Field: "input", Message: fmt.Sprintf("girdi en fazla %d karakter olabilir", MaxInputRunes)},
		}}
	}
	wf, err := s.store.Get(ctx, userID, wfID)
	if err != nil {
		return nil, err
	}
	if errs := Check(wf); len(errs) > 0 {
		return nil, &ValidationError{Errors: errs, NotRunnable: true}
	}
	if missing := s.deps.Missing(wf); len(missing) > 0 {
		return nil, &CapabilityError{Types: missing}
	}

	exec := &Execution{
		ID: id.New("exec"), UserID: userID, WorkflowID: wf.ID, WorkflowName: wf.Name,
		ConversationID: in.ConversationID, Input: preview(in.Input),
		Status: RunRunning, Steps: []Step{}, StartedAt: s.now(),
	}

	s.mu.Lock()
	if s.closed {
		s.mu.Unlock()
		return nil, ErrClosed
	}
	if len(s.running) >= s.opts.MaxConcurrent {
		s.mu.Unlock()
		return nil, ErrBusy
	}
	runCtx, cancel := context.WithTimeout(s.baseCtx, s.opts.RunTimeout)
	s.running[exec.ID] = &runInfo{cancel: cancel, userID: userID, workflowID: wf.ID}
	s.wg.Add(1)
	s.mu.Unlock()

	if err := s.store.CreateExecution(ctx, exec); err != nil {
		cancel()
		s.release(exec.ID)
		s.wg.Done()
		return nil, err
	}

	snapshot := exec.Clone()
	env := RunEnv{UserID: userID, ConversationID: in.ConversationID, Input: in.Input}
	go s.run(runCtx, cancel, wf, exec, env)
	return &snapshot, nil
}

func (s *Service) release(execID string) {
	s.mu.Lock()
	delete(s.running, execID)
	s.mu.Unlock()
}

// run yürütme goroutine'idir: akışı çalıştırır, adımları kalıcılaştırır ve sonucu yazar.
func (s *Service) run(ctx context.Context, cancel context.CancelFunc, wf *Workflow, exec *Execution, env RunEnv) {
	defer s.wg.Done()
	defer s.release(exec.ID)
	defer cancel()

	var persistWarned bool
	runErr := s.safeRun(ctx, wf, exec, env, func(snap Execution) {
		if err := s.persist(&snap); err != nil && !persistWarned && !errors.Is(err, ErrNotFound) {
			persistWarned = true
			s.log.Warn("yürütme ilerlemesi kaydedilemedi", "execution", snap.ID, "err", err)
		}
	})

	now := s.now()
	exec.FinishedAt = &now
	exec.LatencyMs = now.Sub(exec.StartedAt).Milliseconds()
	switch {
	case runErr == nil:
		exec.Status = RunSucceeded
	case errors.Is(runErr, context.Canceled):
		exec.Status = RunCancelled
		if s.baseCtx.Err() != nil {
			exec.Error = "sunucu kapanırken yürütme iptal edildi"
		} else {
			exec.Error = "yürütme iptal edildi"
		}
	case errors.Is(runErr, context.DeadlineExceeded):
		exec.Status = RunFailed
		exec.Error = fmt.Sprintf("zaman aşımı: yürütme %s içinde bitmedi", s.opts.RunTimeout)
	default:
		exec.Status = RunFailed
		exec.Error = runErr.Error()
	}
	// Hâlâ "çalışıyor" görünen adım kalmasın (ör. panikte).
	for i := range exec.Steps {
		if exec.Steps[i].Status == StepRunning {
			exec.Steps[i].Status, exec.Steps[i].Error = StepError, exec.Error
			t := now
			exec.Steps[i].FinishedAt = &t
		}
	}
	// Akış (ve yürütme geçmişi) yürütme sürerken silindiyse kayıt yoktur: beklenen durum, hata değil.
	if err := s.persist(exec); err != nil && !errors.Is(err, ErrNotFound) {
		s.log.Error("yürütme sonucu kaydedilemedi", "execution", exec.ID, "err", err)
	}
	if s.opts.OnFinish != nil {
		s.opts.OnFinish(exec.Clone())
	}
}

// safeRun Run'ı panik kurtarmayla çalıştırır: bir node'daki beklenmeyen panik sunucuyu düşürmez.
func (s *Service) safeRun(ctx context.Context, wf *Workflow, exec *Execution, env RunEnv, notify func(Execution)) (err error) {
	defer func() {
		if rec := recover(); rec != nil {
			s.log.Error("workflow yürütmesinde panic", "execution", exec.ID, "panic", rec)
			err = fmt.Errorf("beklenmeyen hata (panic): %v", rec)
		}
	}()
	return Run(ctx, wf, s.deps, env, exec, notify)
}

// persist yürütme kaydını yazar. İstek bağlamı iptal edilmiş olsa bile yazılır (kendi zaman aşımıyla).
func (s *Service) persist(e *Execution) error {
	ctx, cancel := context.WithTimeout(context.Background(), persistTimeout)
	defer cancel()
	return s.store.UpdateExecution(ctx, e)
}

// Cancel süren bir yürütmeyi iptal eder. İptal ASENKRONDUR: dönen kayıt hâlâ "running" olabilir;
// kesin durum GetExecution ile okunur. Bitmiş yürütme için ErrAlreadyFinished.
func (s *Service) Cancel(ctx context.Context, userID, execID string) (*Execution, error) {
	e, err := s.store.GetExecution(ctx, userID, execID)
	if err != nil {
		return nil, err
	}
	if e.Status.Terminal() {
		return nil, ErrAlreadyFinished
	}
	s.mu.Lock()
	ri, ok := s.running[execID]
	s.mu.Unlock()
	if ok {
		ri.cancel()
		return e, nil
	}
	// Bu süreçte çalışmıyor ama kayıt "running": önceki bir süreçten yetim kalmış.
	now := s.now()
	e.Status, e.Error, e.FinishedAt = RunCancelled, "yürütme iptal edildi (yetim kayıt)", &now
	e.LatencyMs = now.Sub(e.StartedAt).Milliseconds()
	if err := s.store.UpdateExecution(ctx, e); err != nil {
		return nil, err
	}
	return e, nil
}

func (s *Service) GetExecution(ctx context.Context, userID, execID string) (*Execution, error) {
	return s.store.GetExecution(ctx, userID, execID)
}

func (s *Service) ListExecutions(ctx context.Context, userID string, f ExecutionFilter, limit, offset int) ([]Execution, error) {
	return s.store.ListExecutions(ctx, userID, f, limit, offset)
}

// Recover açılışta "running" kalmış yürütmeleri (önceki süreç çöktü/kapandı) failed yapar.
func (s *Service) Recover(ctx context.Context) (int, error) {
	return s.store.FailRunning(ctx, "sunucu yeniden başlatıldı; yürütme yarım kaldı", s.now())
}

// Close yeni yürütmeleri reddeder, süren yürütmeleri iptal eder ve bitmelerini bekler (ctx süresine kadar).
func (s *Service) Close(ctx context.Context) error {
	s.mu.Lock()
	s.closed = true
	s.mu.Unlock()
	s.stop()

	done := make(chan struct{})
	go func() {
		s.wg.Wait()
		close(done)
	}()
	select {
	case <-done:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}
