// Package pregel provides async execution pipeline for Pregel.
package pregel

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"ragflow/internal/harness/graph/types"
)

// AsyncExecutor provides async execution of nodes with concurrency control.
type AsyncExecutor struct {
	maxConcurrency int
	workerPool     chan struct{}
	results        chan *asyncTaskResult
	mu             sync.Mutex
	activeTasks    map[string]*asyncTask
}

// asyncTask represents an asynchronous task.
type asyncTask struct {
	ID       string
	Name     string
	Func     func(context.Context) (any, error)
	Context  context.Context
	Cancel   context.CancelFunc
	Priority int
}

// asyncTaskResult represents the result of an async task.
type asyncTaskResult struct {
	TaskID   string
	Name     string
	Output   any
	Err      error
	Duration time.Duration
}

// NewAsyncExecutor creates a new async executor.
func NewAsyncExecutor(maxConcurrency int) *AsyncExecutor {
	if maxConcurrency <= 0 {
		maxConcurrency = 10 // Default concurrency
	}

	exec := &AsyncExecutor{
		maxConcurrency: maxConcurrency,
		workerPool:     make(chan struct{}, maxConcurrency),
		results:        make(chan *asyncTaskResult, 100),
		activeTasks:    make(map[string]*asyncTask),
	}

	// Pre-fill worker pool
	for i := 0; i < maxConcurrency; i++ {
		exec.workerPool <- struct{}{}
	}

	return exec
}

// Execute executes a single task asynchronously.
func (e *AsyncExecutor) Execute(ctx context.Context, name string, fn func(context.Context) (any, error)) <-chan *asyncTaskResult {
	resultCh := make(chan *asyncTaskResult, 1)

	// Create cancellable context so Cancel() can stop running tasks.
	taskCtx, cancel := context.WithCancel(ctx)
	task := &asyncTask{
		ID:      uuid.New().String(),
		Name:    name,
		Func:    fn,
		Context: taskCtx,
		Cancel:  cancel,
	}

	e.mu.Lock()
	e.activeTasks[task.ID] = task
	e.mu.Unlock()

	go func() {
		defer close(resultCh)
		// Remove from activeTasks on ALL exit paths (success, ctx cancelled, etc.).
		defer func() {
			e.mu.Lock()
			delete(e.activeTasks, task.ID)
			e.mu.Unlock()
		}()

		startTime := time.Now()

		// Acquire worker slot
		select {
		case <-e.workerPool:
			defer func() { e.workerPool <- struct{}{} }()
		case <-ctx.Done():
			resultCh <- &asyncTaskResult{
				TaskID: task.ID,
				Name:   task.Name,
				Err:    ctx.Err(),
			}
			return
		}

		// Execute task
		output, err := task.Func(task.Context)

		result := &asyncTaskResult{
			TaskID:   task.ID,
			Name:     task.Name,
			Output:   output,
			Err:      err,
			Duration: time.Since(startTime),
		}

		resultCh <- result
	}()

	return resultCh
}

// ExecuteBatch executes multiple tasks concurrently with controlled concurrency.
func (e *AsyncExecutor) ExecuteBatch(ctx context.Context, tasks []asyncTask) <-chan *asyncTaskResult {
	resultCh := make(chan *asyncTaskResult, len(tasks))

	// Register all tasks in activeTasks before any goroutine starts.
	for i := range tasks {
		tasks[i].ID = uuid.New().String()
		e.mu.Lock()
		e.activeTasks[tasks[i].ID] = &tasks[i]
		e.mu.Unlock()
	}

	var wg sync.WaitGroup
	for i := range tasks {
		wg.Add(1)
		go func(task *asyncTask) {
			defer wg.Done()
			defer func() {
				e.mu.Lock()
				delete(e.activeTasks, task.ID)
				e.mu.Unlock()
			}()

			startTime := time.Now()

			// Acquire worker slot
			select {
			case <-e.workerPool:
				defer func() { e.workerPool <- struct{}{} }()
			case <-ctx.Done():
				resultCh <- &asyncTaskResult{
					TaskID: task.ID,
					Name:   task.Name,
					Err:    ctx.Err(),
				}
				return
			}

			// Execute task
			output, err := task.Func(task.Context)

			resultCh <- &asyncTaskResult{
				TaskID:   task.ID,
				Name:     task.Name,
				Output:   output,
				Err:      err,
				Duration: time.Since(startTime),
			}
		}(&tasks[i])
	}

	go func() {
		wg.Wait()
		close(resultCh)
	}()

	return resultCh
}

// ExecuteWithRetry executes a task with retry logic.
func (e *AsyncExecutor) ExecuteWithRetry(ctx context.Context, name string, fn func(context.Context) (any, error), retryConfig *RetryConfig) <-chan *asyncTaskResult {
	resultCh := make(chan *asyncTaskResult, 1)

	taskCtx, cancel := context.WithCancel(ctx)
	task := &asyncTask{
		ID:      uuid.New().String(),
		Name:    name,
		Context: taskCtx,
		Cancel:  cancel,
	}

	e.mu.Lock()
	e.activeTasks[task.ID] = task
	e.mu.Unlock()

	go func() {
		defer close(resultCh)

		executor := NewRetryExecutor(retryConfig.Policy)

		startTime := time.Now()
		output, err := executor.Execute(task.Context, name, fn)

		result := &asyncTaskResult{
			TaskID:   task.ID,
			Name:     name,
			Output:   output,
			Err:      err,
			Duration: time.Since(startTime),
		}

		e.mu.Lock()
		delete(e.activeTasks, task.ID)
		e.mu.Unlock()

		resultCh <- result
	}()

	return resultCh
}

// Cancel cancels all active tasks by invoking their cancel functions.
func (e *AsyncExecutor) Cancel() {
	e.mu.Lock()
	defer e.mu.Unlock()

	for id, task := range e.activeTasks {
		if task.Cancel != nil {
			task.Cancel()
		}
		delete(e.activeTasks, id)
	}
}

// GetActiveTaskCount returns the number of currently active tasks.
func (e *AsyncExecutor) GetActiveTaskCount() int {
	e.mu.Lock()
	defer e.mu.Unlock()
	return len(e.activeTasks)
}

// Wait waits for all active tasks to complete.
func (e *AsyncExecutor) Wait(ctx context.Context) error {
	ticker := time.NewTicker(10 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			if e.GetActiveTaskCount() == 0 {
				return nil
			}
		}
	}
}

// AsyncPipeline provides an async execution pipeline for the Pregel loop.
type AsyncPipeline struct {
	executor *AsyncExecutor
	retryer  *RetryExecutor

	// Stream channels
	events chan any
	errors chan error

	// Control
	mu      sync.RWMutex
	cancel  context.CancelFunc
	running bool
}

// NewAsyncPipeline creates a new async pipeline.
func NewAsyncPipeline(maxConcurrency int, retryPolicy *types.RetryPolicy) *AsyncPipeline {
	return &AsyncPipeline{
		executor: NewAsyncExecutor(maxConcurrency),
		retryer:  NewRetryExecutor(retryPolicy),
		events:   make(chan any, 100),
		errors:   make(chan error, 10),
		running:  false,
	}
}

// Start starts the async pipeline.
func (p *AsyncPipeline) Start(ctx context.Context) context.Context {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.running {
		return ctx
	}

	// Reinitialize channels that were closed by Stop().
	p.events = make(chan any, 100)
	p.errors = make(chan error, 10)

	ctx, p.cancel = context.WithCancel(ctx)
	p.running = true

	return ctx
}

// Stop stops the async pipeline.
func (p *AsyncPipeline) Stop() {
	p.mu.Lock()
	defer p.mu.Unlock()

	if !p.running {
		return
	}

	if p.cancel != nil {
		p.cancel()
	}

	p.executor.Cancel()
	close(p.events)
	close(p.errors)
	p.running = false
}

// ExecuteNode executes a node in the pipeline.
func (p *AsyncPipeline) ExecuteNode(ctx context.Context, name string, fn func(context.Context) (any, error), retryConfig *RetryConfig) <-chan *asyncTaskResult {
	if retryConfig != nil {
		return p.executor.ExecuteWithRetry(ctx, name, fn, retryConfig)
	}
	return p.executor.Execute(ctx, name, fn)
}

// Events returns the event channel.
func (p *AsyncPipeline) Events() <-chan any {
	return p.events
}

// Errors returns the error channel.
func (p *AsyncPipeline) Errors() <-chan error {
	return p.errors
}

// EmitEvent emits an event to the pipeline.
func (p *AsyncPipeline) EmitEvent(event any) {
	p.mu.RLock()
	defer p.mu.RUnlock()

	if p.running {
		select {
		case p.events <- event:
		default:
			// Channel full, drop event
		}
	}
}

// EmitError emits an error to the pipeline.
func (p *AsyncPipeline) EmitError(err error) {
	p.mu.RLock()
	defer p.mu.RUnlock()

	if p.running {
		select {
		case p.errors <- err:
		default:
			// Channel full, drop error
		}
	}
}

// IsRunning returns whether the pipeline is running.
func (p *AsyncPipeline) IsRunning() bool {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.running
}

// ConcurrencyLimiter limits concurrency for specific nodes.
type ConcurrencyLimiter struct {
	limits map[string]chan struct{}
	mu     sync.RWMutex
}

// NewConcurrencyLimiter creates a new concurrency limiter.
func NewConcurrencyLimiter() *ConcurrencyLimiter {
	return &ConcurrencyLimiter{
		limits: make(map[string]chan struct{}),
	}
}

// SetLimit sets the concurrency limit for a node.
func (l *ConcurrencyLimiter) SetLimit(node string, limit int) {
	l.mu.Lock()
	defer l.mu.Unlock()

	ch := make(chan struct{}, limit)
	for i := 0; i < limit; i++ {
		ch <- struct{}{}
	}
	l.limits[node] = ch
}

// Acquire acquires a slot for a node.
func (l *ConcurrencyLimiter) Acquire(ctx context.Context, node string) error {
	l.mu.RLock()
	ch, ok := l.limits[node]
	l.mu.RUnlock()

	if !ok {
		// No limit set
		return nil
	}

	select {
	case <-ch:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// Release releases a slot for a node.
func (l *ConcurrencyLimiter) Release(node string) {
	l.mu.RLock()
	ch, ok := l.limits[node]
	l.mu.RUnlock()

	if !ok {
		return
	}

	select {
	case ch <- struct{}{}:
	default:
		// Channel full, shouldn't happen
	}
}

// PriorityTask represents a prioritized task.
type PriorityTask struct {
	Func     func(context.Context) (any, error)
	Priority int
}

// PriorityExecutor executes tasks with priority scheduling.
type PriorityExecutor struct {
	tasks chan PriorityTask
	mu    sync.Mutex
}

// NewPriorityExecutor creates a new priority executor.
func NewPriorityExecutor(bufferSize int) *PriorityExecutor {
	return &PriorityExecutor{
		tasks: make(chan PriorityTask, bufferSize),
	}
}

// Submit submits a task with priority.
func (e *PriorityExecutor) Submit(task PriorityTask) error {
	select {
	case e.tasks <- task:
		return nil
	default:
		return fmt.Errorf("task queue full")
	}
}

// Execute executes tasks in priority order.
func (e *PriorityExecutor) Execute(ctx context.Context, maxConcurrency int) <-chan any {
	resultCh := make(chan any, maxConcurrency)

	// Simple priority scheduling using multiple channels
	// In a real implementation, you'd use a priority queue
	go func() {
		defer close(resultCh)

		// This is a simplified implementation
		// A full implementation would use a heap-based priority queue
		for {
			select {
			case <-ctx.Done():
				return
			case task := <-e.tasks:
				output, err := task.Func(ctx)
				resultCh <- map[string]any{
					"output":   output,
					"error":    err,
					"priority": task.Priority,
				}
			}
		}
	}()

	return resultCh
}
