package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"gitea.dockr.co/kev/go-jdenticon/jdenticon"
)

// benchmarkSizes defines different test scenarios for batch processing
var benchmarkSizes = []struct {
	name  string
	count int
}{
	{"Small", 50},
	{"Medium", 200},
	{"Large", 1000},
}

// BenchmarkBatchProcessing_Sequential benchmarks sequential processing (concurrency=1)
func BenchmarkBatchProcessing_Sequential(b *testing.B) {
	for _, size := range benchmarkSizes {
		b.Run(size.name, func(b *testing.B) {
			benchmarkBatchWithConcurrency(b, size.count, 1)
		})
	}
}

// BenchmarkBatchProcessing_Concurrent benchmarks concurrent processing with different worker counts
func BenchmarkBatchProcessing_Concurrent(b *testing.B) {
	concurrencyLevels := []int{2, 4, runtime.NumCPU(), runtime.NumCPU() * 2}

	for _, size := range benchmarkSizes {
		for _, concurrency := range concurrencyLevels {
			b.Run(fmt.Sprintf("%s_Workers%d", size.name, concurrency), func(b *testing.B) {
				benchmarkBatchWithConcurrency(b, size.count, concurrency)
			})
		}
	}
}

// benchmarkBatchWithConcurrency runs a benchmark with specific parameters
func benchmarkBatchWithConcurrency(b *testing.B, iconCount, concurrency int) {
	// Create temporary directory for test
	tempDir := b.TempDir()
	inputFile := filepath.Join(tempDir, "test-input.txt")
	outputDir := filepath.Join(tempDir, "output")

	// Generate test input file
	createTestInputFile(b, inputFile, iconCount)

	// Create generator for testing with complexity limits disabled for consistent benchmarks
	config, err := jdenticon.Configure(jdenticon.WithMaxComplexity(-1))
	if err != nil {
		b.Fatalf("Failed to create config: %v", err)
	}
	generator, err := jdenticon.NewGeneratorWithConfig(config, concurrency*100)
	if err != nil {
		b.Fatalf("Failed to create generator: %v", err)
	}

	b.ResetTimer()
	b.ReportAllocs()

	for i := 0; i < b.N; i++ {
		// Clean and recreate output directory for each iteration
		os.RemoveAll(outputDir)
		if err := os.MkdirAll(outputDir, 0755); err != nil {
			b.Fatalf("Failed to create output directory: %v", err)
		}

		// Measure processing time
		start := time.Now()

		// Execute batch processing
		jobs, total, err := prepareJobs(inputFile, outputDir, FormatSVG, 64)
		if err != nil {
			b.Fatalf("Failed to prepare jobs: %v", err)
		}

		if total != iconCount {
			b.Fatalf("Expected %d jobs, got %d", iconCount, total)
		}

		// Process jobs (simplified version without progress bar for benchmarking)
		stats := processBenchmarkJobs(jobs, generator, FormatSVG, concurrency)

		duration := time.Since(start)

		// Verify all jobs completed successfully
		processed := atomic.LoadInt64(&stats.processed)
		failed := atomic.LoadInt64(&stats.failed)

		if processed != int64(iconCount) {
			b.Fatalf("Expected %d processed, got %d", iconCount, processed)
		}

		if failed > 0 {
			b.Fatalf("Expected 0 failures, got %d", failed)
		}

		// Report custom metrics
		b.ReportMetric(float64(iconCount)/duration.Seconds(), "icons/sec")
		b.ReportMetric(float64(concurrency), "workers")
	}
}

// processBenchmarkJobs executes jobs for benchmarking (without context cancellation)
func processBenchmarkJobs(jobs []batchJob, generator *jdenticon.Generator, format FormatFlag, concurrency int) *batchStats {
	stats := &batchStats{}
	jobChan := make(chan batchJob, len(jobs))

	// Start workers
	done := make(chan struct{})
	for i := 0; i < concurrency; i++ {
		go func() {
			defer func() { done <- struct{}{} }()
			for job := range jobChan {
				if err := processJob(context.Background(), job, generator, format); err != nil {
					atomic.AddInt64(&stats.failed, 1)
				} else {
					atomic.AddInt64(&stats.processed, 1)
				}
			}
		}()
	}

	// Send jobs
	go func() {
		defer close(jobChan)
		for _, job := range jobs {
			jobChan <- job
		}
	}()

	// Wait for completion
	for i := 0; i < concurrency; i++ {
		<-done
	}

	return stats
}

// createTestInputFile generates a test input file with specified number of entries
func createTestInputFile(b *testing.B, filename string, count int) {
	file, err := os.Create(filename)
	if err != nil {
		b.Fatalf("Failed to create test input file: %v", err)
	}
	defer file.Close()

	var builder strings.Builder
	for i := 0; i < count; i++ {
		builder.WriteString(fmt.Sprintf("user%d@example.com\n", i))
	}

	if _, err := file.WriteString(builder.String()); err != nil {
		b.Fatalf("Failed to write test input file: %v", err)
	}
}

// BenchmarkJobPreparation benchmarks the job preparation phase
func BenchmarkJobPreparation(b *testing.B) {
	for _, size := range benchmarkSizes {
		b.Run(size.name, func(b *testing.B) {
			tempDir := b.TempDir()
			inputFile := filepath.Join(tempDir, "test-input.txt")
			outputDir := filepath.Join(tempDir, "output")

			createTestInputFile(b, inputFile, size.count)

			b.ResetTimer()
			b.ReportAllocs()

			for i := 0; i < b.N; i++ {
				jobs, total, err := prepareJobs(inputFile, outputDir, FormatSVG, 64)
				if err != nil {
					b.Fatalf("Failed to prepare jobs: %v", err)
				}

				if total != size.count {
					b.Fatalf("Expected %d jobs, got %d", size.count, total)
				}

				// Prevent compiler optimization
				_ = jobs
			}
		})
	}
}

// BenchmarkSingleJobProcessing benchmarks individual job processing
func BenchmarkSingleJobProcessing(b *testing.B) {
	tempDir := b.TempDir()
	config, err := jdenticon.Configure(jdenticon.WithMaxComplexity(-1))
	if err != nil {
		b.Fatalf("Failed to create config: %v", err)
	}
	generator, err := jdenticon.NewGeneratorWithConfig(config, 100)
	if err != nil {
		b.Fatalf("Failed to create generator: %v", err)
	}

	job := batchJob{
		value:      "test@example.com",
		outputPath: filepath.Join(tempDir, "test.svg"),
		size:       64,
	}

	b.ResetTimer()
	b.ReportAllocs()

	for i := 0; i < b.N; i++ {
		if err := processJob(context.Background(), job, generator, FormatSVG); err != nil {
			b.Fatalf("Failed to process job: %v", err)
		}

		// Clean up for next iteration
		os.Remove(job.outputPath)
	}
}

// BenchmarkConcurrencyScaling analyzes how performance scales with worker count
func BenchmarkConcurrencyScaling(b *testing.B) {
	const iconCount = 500
	maxWorkers := runtime.NumCPU() * 2

	for workers := 1; workers <= maxWorkers; workers *= 2 {
		b.Run(fmt.Sprintf("Workers%d", workers), func(b *testing.B) {
			benchmarkBatchWithConcurrency(b, iconCount, workers)
		})
	}
}
