package batching

import (
	"fmt"
	"sync"
	"time"
)

// GitOp represents a git operation to be batched
type GitOp struct {
	OpType    string // "commit", "push", "merge"
	Branch    string
	Message   string
	Files     []string
	Timestamp time.Time
	ID        string
}

// GitBatch represents a batch of git operations
type GitBatch struct {
	ID         string
	Operations []*GitOp
	CreatedAt  time.Time
	ExecutedAt time.Time
	Status     string // "pending", "executing", "completed", "failed"
	Error      error
}

// GitBatcher batches git operations for efficient execution
type GitBatcher struct {
	mu                sync.RWMutex
	queue             []*GitOp
	maxBatchSize      int
	maxBatchAge       time.Duration
	lastFlushTime     time.Time
	executedBatches   []*GitBatch
	pendingBatches    []*GitBatch
	stats             *BatchStats
	flushChan         chan struct{}
	stopChan          chan struct{}
}

// BatchStats tracks batching statistics
type BatchStats struct {
	TotalOps        int
	TotalBatches    int
	AvgOpsPerBatch  float64
	NetworkSavings  int // Estimated network round trips saved
	TotalExecuteTime time.Duration
}

// NewGitBatcher creates a new git batcher
func NewGitBatcher(maxBatchSize int, maxBatchAge time.Duration) *GitBatcher {
	if maxBatchSize <= 0 {
		maxBatchSize = 10
	}
	if maxBatchAge <= 0 {
		maxBatchAge = 5 * time.Second
	}

	return &GitBatcher{
		queue:           make([]*GitOp, 0),
		maxBatchSize:    maxBatchSize,
		maxBatchAge:     maxBatchAge,
		lastFlushTime:   time.Now(),
		executedBatches: make([]*GitBatch, 0),
		pendingBatches:  make([]*GitBatch, 0),
		stats: &BatchStats{
			TotalOps:     0,
			TotalBatches: 0,
		},
		flushChan: make(chan struct{}, 1),
		stopChan:  make(chan struct{}),
	}
}

// Enqueue adds a git operation to the queue
func (gb *GitBatcher) Enqueue(op *GitOp) {
	if op == nil {
		return
	}

	op.Timestamp = time.Now()

	gb.mu.Lock()
	defer gb.mu.Unlock()

	gb.queue = append(gb.queue, op)
	gb.stats.TotalOps++

	// Auto-flush if batch is full
	if len(gb.queue) >= gb.maxBatchSize {
		gb.flushLocked()
	}
}

// flushLocked creates a batch from queued operations (must be called with lock held)
func (gb *GitBatcher) flushLocked() {
	if len(gb.queue) == 0 {
		return
	}

	batch := &GitBatch{
		ID:         fmt.Sprintf("batch-%d", gb.stats.TotalBatches),
		Operations: make([]*GitOp, len(gb.queue)),
		CreatedAt:  time.Now(),
		Status:     "pending",
	}

	copy(batch.Operations, gb.queue)

	gb.pendingBatches = append(gb.pendingBatches, batch)
	gb.queue = make([]*GitOp, 0)
	gb.lastFlushTime = time.Now()
	gb.stats.TotalBatches++
}

// Flush manually flushes the current batch
func (gb *GitBatcher) Flush() {
	gb.mu.Lock()
	defer gb.mu.Unlock()

	gb.flushLocked()
}

// GetPendingBatch returns the next pending batch without removing it
func (gb *GitBatcher) GetPendingBatch() *GitBatch {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	if len(gb.pendingBatches) == 0 {
		return nil
	}

	return gb.pendingBatches[0]
}

// MarkBatchExecuting marks a batch as executing
func (gb *GitBatcher) MarkBatchExecuting(batchID string) {
	gb.mu.Lock()
	defer gb.mu.Unlock()

	for _, batch := range gb.pendingBatches {
		if batch.ID == batchID {
			batch.Status = "executing"
			break
		}
	}
}

// MarkBatchCompleted marks a batch as completed and removes from pending
func (gb *GitBatcher) MarkBatchCompleted(batchID string) {
	gb.mu.Lock()
	defer gb.mu.Unlock()

	var idx int
	var found *GitBatch
	for i, batch := range gb.pendingBatches {
		if batch.ID == batchID {
			idx = i
			found = batch
			break
		}
	}

	if found != nil {
		found.Status = "completed"
		found.ExecutedAt = time.Now()

		// Move to executed batches
		gb.executedBatches = append(gb.executedBatches, found)

		// Remove from pending
		gb.pendingBatches = append(gb.pendingBatches[:idx], gb.pendingBatches[idx+1:]...)
	}
}

// MarkBatchFailed marks a batch as failed with an error
func (gb *GitBatcher) MarkBatchFailed(batchID string, err error) {
	gb.mu.Lock()
	defer gb.mu.Unlock()

	var found *GitBatch
	for _, batch := range gb.pendingBatches {
		if batch.ID == batchID {
			found = batch
			break
		}
	}

	if found != nil {
		found.Status = "failed"
		found.Error = err
		found.ExecutedAt = time.Now()

		// Keep in pending (for retry logic)
		// Could also move to failed queue
	}
}

// QueueSize returns the current queue size
func (gb *GitBatcher) QueueSize() int {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	return len(gb.queue)
}

// PendingBatchCount returns the number of pending batches
func (gb *GitBatcher) PendingBatchCount() int {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	return len(gb.pendingBatches)
}

// GetStats returns batching statistics
func (gb *GitBatcher) GetStats() *BatchStats {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	stats := *gb.stats
	if stats.TotalBatches > 0 {
		stats.AvgOpsPerBatch = float64(stats.TotalOps) / float64(stats.TotalBatches)
		// Estimated savings: each batch saves (ops-1) round trips
		stats.NetworkSavings = stats.TotalOps - stats.TotalBatches
	}

	return &stats
}

// GetExecutedBatches returns all executed batches
func (gb *GitBatcher) GetExecutedBatches() []*GitBatch {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	result := make([]*GitBatch, len(gb.executedBatches))
	copy(result, gb.executedBatches)

	return result
}

// GetBatchByID returns a specific batch by ID
func (gb *GitBatcher) GetBatchByID(batchID string) *GitBatch {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	for _, batch := range gb.pendingBatches {
		if batch.ID == batchID {
			return batch
		}
	}

	for _, batch := range gb.executedBatches {
		if batch.ID == batchID {
			return batch
		}
	}

	return nil
}

// TimeSinceLastFlush returns time since last flush
func (gb *GitBatcher) TimeSinceLastFlush() time.Duration {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	return time.Since(gb.lastFlushTime)
}

// ShouldFlush checks if batch should be flushed based on age
func (gb *GitBatcher) ShouldFlush() bool {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	if len(gb.queue) == 0 {
		return false
	}

	return time.Since(gb.lastFlushTime) >= gb.maxBatchAge
}

// Clear clears all pending operations and batches
func (gb *GitBatcher) Clear() {
	gb.mu.Lock()
	defer gb.mu.Unlock()

	gb.queue = make([]*GitOp, 0)
	gb.pendingBatches = make([]*GitBatch, 0)
	gb.executedBatches = make([]*GitBatch, 0)
}

// GetQueuedOps returns a copy of queued operations
func (gb *GitBatcher) GetQueuedOps() []*GitOp {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	ops := make([]*GitOp, len(gb.queue))
	copy(ops, gb.queue)

	return ops
}

// CalculateNetworkSavings calculates estimated network round trips saved
func (gb *GitBatcher) CalculateNetworkSavings() int {
	gb.mu.RLock()
	defer gb.mu.RUnlock()

	totalSavings := 0
	// Each batch of N operations saves N-1 round trips
	for _, batch := range gb.executedBatches {
		if len(batch.Operations) > 1 {
			totalSavings += len(batch.Operations) - 1
		}
	}

	return totalSavings
}

// GetBatchInfo returns human-readable batch information
func (batch *GitBatch) GetInfo() map[string]interface{} {
	return map[string]interface{}{
		"id":          batch.ID,
		"status":      batch.Status,
		"op_count":    len(batch.Operations),
		"created_at":  batch.CreatedAt,
		"executed_at": batch.ExecutedAt,
		"duration":    batch.ExecutedAt.Sub(batch.CreatedAt),
		"error":       batch.Error,
	}
}
