// Package pregel provides background execution support for Pregel.
package pregel

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

	"github.com/google/uuid"
)

// BackgroundTask represents a background task.
type BackgroundTask struct {
	ID       string
	Name     string
	Func     func(context.Context) (any, error)
	Context  context.Context
	Result   chan *BackgroundTaskResult
	Cancel   context.CancelFunc
	Priority int
	Created  time.Time
}

// BackgroundTaskResult represents the result of a background task.
type BackgroundTaskResult struct {
	TaskID   string
	Name     string
	Output   any
	Err      error
	Duration time.Duration
}

// BackgroundExecutor executes tasks in a background worker pool.
type BackgroundExecutor struct {
	maxWorkers      int
	taskQueue       chan *BackgroundTask
	results         chan *BackgroundTaskResult
	workers         []*backgroundWorker
	mu              sync.RWMutex
	running         bool
	stopCh          chan struct{}
	wg              sync.WaitGroup
	activeTasks     map[string]*BackgroundTask
	shutdownTimeout time.Duration
}

// backgroundWorker represents a worker goroutine.
type backgroundWorker struct {
	id       int
	executor *BackgroundExecutor
	stopCh   chan struct{}
}

// NewBackgroundExecutor creates a new background executor.
func NewBackgroundExecutor(maxWorkers int, queueSize int) *BackgroundExecutor {
	if maxWorkers <= 0 {
		maxWorkers = 10
	}
	if queueSize <= 0 {
		queueSize = 100
	}

	return &BackgroundExecutor{
		maxWorkers:      maxWorkers,
		taskQueue:       make(chan *BackgroundTask, queueSize),
		results:         make(chan *BackgroundTaskResult, queueSize),
		workers:         make([]*backgroundWorker, 0, maxWorkers),
		stopCh:          make(chan struct{}),
		activeTasks:     make(map[string]*BackgroundTask),
		shutdownTimeout: 30 * time.Second,
	}
}

// Start starts the background executor.
func (e *BackgroundExecutor) Start(ctx context.Context) {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.running {
		return
	}

	e.running = true
	e.stopCh = make(chan struct{})

	// Start workers
	for i := 0; i < e.maxWorkers; i++ {
		worker := &backgroundWorker{
			id:       i,
			executor: e,
			stopCh:   make(chan struct{}),
		}
		e.workers = append(e.workers, worker)
		e.wg.Add(1)
		go worker.run()
	}
}

// Stop stops the background executor gracefully.
func (e *BackgroundExecutor) Stop() {
	e.mu.Lock()
	if !e.running {
		e.mu.Unlock()
		return
	}
	e.running = false
	close(e.stopCh)
	e.mu.Unlock()

	// Wait for workers to finish with timeout
	done := make(chan struct{})
	go func() {
		e.wg.Wait()
		close(done)
	}()

	select {
	case <-done:
		// All workers stopped
	case <-time.After(e.shutdownTimeout):
		// Timeout - force stop
	}

	close(e.taskQueue)
	close(e.results)
}

// Submit submits a task for background execution.
func (e *BackgroundExecutor) Submit(ctx context.Context, name string, fn func(context.Context) (any, error), priority int) (*BackgroundTask, error) {
	e.mu.RLock()
	if !e.running {
		e.mu.RUnlock()
		return nil, fmt.Errorf("executor not running")
	}
	e.mu.RUnlock()

	taskCtx, cancel := context.WithCancel(ctx)
	task := &BackgroundTask{
		ID:       uuid.New().String(),
		Name:     name,
		Func:     fn,
		Context:  taskCtx,
		Result:   make(chan *BackgroundTaskResult, 1),
		Cancel:   cancel,
		Priority: priority,
		Created:  time.Now(),
	}

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

	select {
	case e.taskQueue <- task:
		return task, nil
	case <-ctx.Done():
		cancel()
		return nil, ctx.Err()
	case <-time.After(5 * time.Second):
		cancel()
		return nil, fmt.Errorf("task queue full")
	}
}

// GetResult gets the result channel for receiving task results.
func (e *BackgroundExecutor) GetResult() <-chan *BackgroundTaskResult {
	return e.results
}

// CancelTask cancels a specific task.
func (e *BackgroundExecutor) CancelTask(taskID string) bool {
	e.mu.Lock()
	defer e.mu.Unlock()

	if task, ok := e.activeTasks[taskID]; ok {
		task.Cancel()
		return true
	}
	return false
}

// GetActiveTasks returns the list of active task IDs.
func (e *BackgroundExecutor) GetActiveTasks() []string {
	e.mu.RLock()
	defer e.mu.RUnlock()

	tasks := make([]string, 0, len(e.activeTasks))
	for id := range e.activeTasks {
		tasks = append(tasks, id)
	}
	return tasks
}

// run is the worker goroutine.
func (w *backgroundWorker) run() {
	defer w.executor.wg.Done()

	for {
		select {
		case <-w.executor.stopCh:
			return
		case task, ok := <-w.executor.taskQueue:
			if !ok {
				return
			}
			w.executeTask(task)
		}
	}
}

// executeTask executes a single task.
func (w *backgroundWorker) executeTask(task *BackgroundTask) {
	startTime := time.Now()

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

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

	// Remove from active tasks
	w.executor.mu.Lock()
	delete(w.executor.activeTasks, task.ID)
	w.executor.mu.Unlock()

	// Send result
	select {
	case task.Result <- result:
	case <-time.After(5 * time.Second):
		// Task result channel blocked
	}

	// Also send to global results channel
	select {
	case w.executor.results <- result:
	case <-time.After(5 * time.Second):
		// Results channel blocked
	}
}

// TaskPriority constants for common priorities.
const (
	PriorityLow      = 0
	PriorityNormal   = 5
	PriorityHigh     = 10
	PriorityCritical = 20
)

// PriorityQueue implements a priority queue for tasks.
type PriorityQueue struct {
	tasks []*BackgroundTask
	mu    sync.RWMutex
}

// NewPriorityQueue creates a new priority queue.
func NewPriorityQueue() *PriorityQueue {
	return &PriorityQueue{
		tasks: make([]*BackgroundTask, 0),
	}
}

// Push adds a task to the queue.
func (pq *PriorityQueue) Push(task *BackgroundTask) {
	pq.mu.Lock()
	defer pq.mu.Unlock()

	pq.tasks = append(pq.tasks, task)
	pq.heapifyUp(len(pq.tasks) - 1)
}

// Pop removes and returns the highest priority task.
func (pq *PriorityQueue) Pop() (*BackgroundTask, bool) {
	pq.mu.Lock()
	defer pq.mu.Unlock()

	if len(pq.tasks) == 0 {
		return nil, false
	}

	task := pq.tasks[0]
	pq.tasks[0] = pq.tasks[len(pq.tasks)-1]
	pq.tasks = pq.tasks[:len(pq.tasks)-1]

	if len(pq.tasks) > 0 {
		pq.heapifyDown(0)
	}

	return task, true
}

// Len returns the number of tasks in the queue.
func (pq *PriorityQueue) Len() int {
	pq.mu.RLock()
	defer pq.mu.RUnlock()
	return len(pq.tasks)
}

// heapifyUp maintains heap property when adding.
func (pq *PriorityQueue) heapifyUp(index int) {
	for index > 0 {
		parent := (index - 1) / 2
		if pq.tasks[parent].Priority >= pq.tasks[index].Priority {
			break
		}
		pq.tasks[parent], pq.tasks[index] = pq.tasks[index], pq.tasks[parent]
		index = parent
	}
}

// heapifyDown maintains heap property when removing.
func (pq *PriorityQueue) heapifyDown(index int) {
	n := len(pq.tasks)
	for {
		largest := index
		left := 2*index + 1
		right := 2*index + 2

		if left < n && pq.tasks[left].Priority > pq.tasks[largest].Priority {
			largest = left
		}
		if right < n && pq.tasks[right].Priority > pq.tasks[largest].Priority {
			largest = right
		}
		if largest == index {
			break
		}
		pq.tasks[index], pq.tasks[largest] = pq.tasks[largest], pq.tasks[index]
		index = largest
	}
}
