package workflow

import (
	"context"
	"errors"
	"sort"
	"strings"
	"sync"
	"time"
)

// MemoryRepo bellek içi Store uygulamasıdır (MongoDB yokken geliştirme ve testler).
// Eşzamanlı kullanıma güvenlidir; giriş/çıkışta derin kopya alır.
type MemoryRepo struct {
	mu         sync.RWMutex
	workflows  map[string]Workflow
	executions map[string]Execution
}

var _ Store = (*MemoryRepo)(nil)

func NewMemoryRepo() *MemoryRepo {
	return &MemoryRepo{workflows: map[string]Workflow{}, executions: map[string]Execution{}}
}

func (r *MemoryRepo) Create(_ context.Context, w *Workflow) error {
	if w == nil || w.ID == "" {
		return errors.New("workflow: kimlik gerekli")
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, ok := r.workflows[w.ID]; ok {
		return errors.New("workflow: bu kimlik zaten var")
	}
	r.workflows[w.ID] = w.Clone()
	return nil
}

func (r *MemoryRepo) Get(_ context.Context, userID, id string) (*Workflow, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	w, ok := r.workflows[id]
	if !ok || w.UserID != userID {
		return nil, ErrNotFound
	}
	c := w.Clone()
	return &c, nil
}

func (r *MemoryRepo) Update(_ context.Context, w *Workflow) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	old, ok := r.workflows[w.ID]
	if !ok || old.UserID != w.UserID {
		return ErrNotFound
	}
	r.workflows[w.ID] = w.Clone()
	return nil
}

func (r *MemoryRepo) Delete(_ context.Context, userID, id string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	w, ok := r.workflows[id]
	if !ok || w.UserID != userID {
		return ErrNotFound
	}
	delete(r.workflows, id)
	return nil
}

func (r *MemoryRepo) List(_ context.Context, userID string, f ListFilter, limit, offset int) ([]Workflow, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	q := strings.ToLower(strings.TrimSpace(f.Query))
	out := make([]Workflow, 0)
	for _, w := range r.workflows {
		if w.UserID != userID {
			continue
		}
		if q != "" && !strings.Contains(strings.ToLower(w.Name), q) && !strings.Contains(strings.ToLower(w.Description), q) {
			continue
		}
		out = append(out, w.Clone())
	}
	sort.Slice(out, func(i, j int) bool {
		if !out[i].UpdatedAt.Equal(out[j].UpdatedAt) {
			return out[i].UpdatedAt.After(out[j].UpdatedAt)
		}
		return out[i].ID < out[j].ID
	})
	return page(out, limit, offset), nil
}

func (r *MemoryRepo) CreateExecution(_ context.Context, e *Execution) error {
	if e == nil || e.ID == "" {
		return errors.New("workflow: yürütme kimliği gerekli")
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, ok := r.executions[e.ID]; ok {
		return errors.New("workflow: bu yürütme kimliği zaten var")
	}
	r.executions[e.ID] = e.Clone()
	return nil
}

func (r *MemoryRepo) GetExecution(_ context.Context, userID, id string) (*Execution, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	e, ok := r.executions[id]
	if !ok || e.UserID != userID {
		return nil, ErrNotFound
	}
	c := e.Clone()
	return &c, nil
}

func (r *MemoryRepo) UpdateExecution(_ context.Context, e *Execution) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	old, ok := r.executions[e.ID]
	if !ok || old.UserID != e.UserID {
		return ErrNotFound
	}
	r.executions[e.ID] = e.Clone()
	return nil
}

func (r *MemoryRepo) ListExecutions(_ context.Context, userID string, f ExecutionFilter, limit, offset int) ([]Execution, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]Execution, 0)
	for _, e := range r.executions {
		if e.UserID != userID {
			continue
		}
		if f.WorkflowID != "" && e.WorkflowID != f.WorkflowID {
			continue
		}
		if f.Status != "" && e.Status != f.Status {
			continue
		}
		out = append(out, e.Clone())
	}
	sort.Slice(out, func(i, j int) bool {
		if !out[i].StartedAt.Equal(out[j].StartedAt) {
			return out[i].StartedAt.After(out[j].StartedAt)
		}
		return out[i].ID < out[j].ID
	})
	return page(out, limit, offset), nil
}

func (r *MemoryRepo) DeleteExecutionsByWorkflow(_ context.Context, userID, workflowID string) (int, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	n := 0
	for id, e := range r.executions {
		if e.UserID == userID && e.WorkflowID == workflowID {
			delete(r.executions, id)
			n++
		}
	}
	return n, nil
}

func (r *MemoryRepo) FailRunning(_ context.Context, reason string, at time.Time) (int, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	n := 0
	for id, e := range r.executions {
		if e.Status != RunRunning {
			continue
		}
		e.Status, e.Error = RunFailed, reason
		t := at
		e.FinishedAt = &t
		e.LatencyMs = at.Sub(e.StartedAt).Milliseconds()
		// Yarım kalan adımları da kapat: arayüzde sonsuza dek "çalışıyor" görünmesin.
		for i := range e.Steps {
			if e.Steps[i].Status == StepRunning {
				e.Steps[i].Status, e.Steps[i].Error = StepError, reason
				st := at
				e.Steps[i].FinishedAt = &st
			}
		}
		r.executions[id] = e
		n++
	}
	return n, nil
}

// page offset/limit uygular; sonuç asla nil değildir.
func page[T any](in []T, limit, offset int) []T {
	if offset < 0 {
		offset = 0
	}
	if offset >= len(in) {
		return []T{}
	}
	in = in[offset:]
	if limit > 0 && limit < len(in) {
		in = in[:limit]
	}
	return in
}
