package main

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

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

// testBinaryName returns the correct test binary name for the current OS.
// On Windows, executables need the .exe extension.
func testBinaryName() string {
	if runtime.GOOS == "windows" {
		return "jdenticon-test.exe"
	}
	return "jdenticon-test"
}

// TestCLIVsLibraryOutputIdentical verifies that CLI generates identical output to the library API
func TestCLIVsLibraryOutputIdentical(t *testing.T) {
	// Build the CLI binary first
	tempDir, err := os.MkdirTemp("", "jdenticon-integration-test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	cliBinary := filepath.Join(tempDir, testBinaryName())
	cmd := exec.Command("go", "build", "-o", cliBinary, ".")
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to build CLI binary: %v", err)
	}

	testCases := []struct {
		name       string
		input      string
		size       int
		cliArgs    []string
		configFunc func() jdenticon.Config
	}{
		{
			name:    "basic SVG generation",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "test@example.com"},
			configFunc: func() jdenticon.Config {
				return jdenticon.DefaultConfig()
			},
		},
		{
			name:    "custom size",
			input:   "test@example.com",
			size:    128,
			cliArgs: []string{"generate", "--size", "128", "test@example.com"},
			configFunc: func() jdenticon.Config {
				return jdenticon.DefaultConfig()
			},
		},
		{
			name:    "custom padding",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "--padding", "0.15", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.Padding = 0.15
				return config
			},
		},
		{
			name:    "custom color saturation",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "--color-saturation", "0.8", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.ColorSaturation = 0.8
				return config
			},
		},
		{
			name:    "background color",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "--bg-color", "#ffffff", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.BackgroundColor = "#ffffff"
				return config
			},
		},
		{
			name:    "grayscale saturation",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "--grayscale-saturation", "0.1", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.GrayscaleSaturation = 0.1
				return config
			},
		},
		{
			name:    "custom lightness ranges",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "--color-lightness", "0.3,0.7", "--grayscale-lightness", "0.2,0.8", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.ColorLightnessRange = [2]float64{0.3, 0.7}
				config.GrayscaleLightnessRange = [2]float64{0.2, 0.8}
				return config
			},
		},
		{
			name:    "hue restrictions",
			input:   "test@example.com",
			size:    200,
			cliArgs: []string{"generate", "--hue-restrictions", "0,120,240", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.HueRestrictions = []float64{0, 120, 240}
				return config
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Generate using library API
			config := tc.configFunc()
			librarySVG, err := jdenticon.ToSVGWithConfig(context.Background(), tc.input, tc.size, config)
			if err != nil {
				t.Fatalf("Library generation failed: %v", err)
			}

			// Generate using CLI
			cmd := exec.Command(cliBinary, tc.cliArgs...)
			var cliOutput bytes.Buffer
			cmd.Stdout = &cliOutput
			cmd.Stderr = &cliOutput

			if err := cmd.Run(); err != nil {
				t.Fatalf("CLI command failed: %v, output: %s", err, cliOutput.String())
			}

			cliSVG := cliOutput.String()

			// Compare outputs
			if cliSVG != librarySVG {
				t.Errorf("CLI and library outputs differ")
				t.Logf("Library output length: %d", len(librarySVG))
				t.Logf("CLI output length: %d", len(cliSVG))

				// Find the first difference
				minLen := len(librarySVG)
				if len(cliSVG) < minLen {
					minLen = len(cliSVG)
				}

				for i := 0; i < minLen; i++ {
					if librarySVG[i] != cliSVG[i] {
						start := i - 20
						if start < 0 {
							start = 0
						}
						end := i + 20
						if end > minLen {
							end = minLen
						}

						t.Logf("First difference at position %d:", i)
						t.Logf("Library: %q", librarySVG[start:end])
						t.Logf("CLI:     %q", cliSVG[start:end])
						break
					}
				}
			}
		})
	}
}

// TestCLIPNGVsLibraryOutputIdentical verifies PNG output consistency
func TestCLIPNGVsLibraryOutputIdentical(t *testing.T) {
	// Build the CLI binary first
	tempDir, err := os.MkdirTemp("", "jdenticon-png-integration-test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	cliBinary := filepath.Join(tempDir, testBinaryName())
	cmd := exec.Command("go", "build", "-o", cliBinary, ".")
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to build CLI binary: %v", err)
	}

	testCases := []struct {
		name       string
		input      string
		size       int
		cliArgs    []string
		configFunc func() jdenticon.Config
	}{
		{
			name:    "basic PNG generation",
			input:   "test@example.com",
			size:    64,
			cliArgs: []string{"generate", "--format", "png", "--size", "64", "test@example.com"},
			configFunc: func() jdenticon.Config {
				return jdenticon.DefaultConfig()
			},
		},
		{
			name:    "PNG with background",
			input:   "test@example.com",
			size:    64,
			cliArgs: []string{"generate", "--format", "png", "--size", "64", "--bg-color", "#ff0000", "test@example.com"},
			configFunc: func() jdenticon.Config {
				config := jdenticon.DefaultConfig()
				config.BackgroundColor = "#ff0000"
				return config
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Generate using library API
			config := tc.configFunc()
			libraryPNG, err := jdenticon.ToPNGWithConfig(context.Background(), tc.input, tc.size, config)
			if err != nil {
				t.Fatalf("Library PNG generation failed: %v", err)
			}

			// Generate using CLI
			cmd := exec.Command(cliBinary, tc.cliArgs...)
			var cliOutput bytes.Buffer
			cmd.Stdout = &cliOutput

			if err := cmd.Run(); err != nil {
				t.Fatalf("CLI command failed: %v", err)
			}

			cliPNG := cliOutput.Bytes()

			// Compare PNG outputs - they should be identical
			if !bytes.Equal(cliPNG, libraryPNG) {
				t.Errorf("CLI and library PNG outputs differ")
				t.Logf("Library PNG size: %d bytes", len(libraryPNG))
				t.Logf("CLI PNG size: %d bytes", len(cliPNG))

				// Check PNG headers
				if len(libraryPNG) >= 8 && len(cliPNG) >= 8 {
					if !bytes.Equal(libraryPNG[:8], cliPNG[:8]) {
						t.Logf("PNG headers differ")
						t.Logf("Library: %v", libraryPNG[:8])
						t.Logf("CLI: %v", cliPNG[:8])
					}
				}
			}
		})
	}
}

// TestCLIBatchIntegration tests batch processing consistency
func TestCLIBatchIntegration(t *testing.T) {
	// Build the CLI binary first
	tempDir, err := os.MkdirTemp("", "jdenticon-batch-integration-test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	cliBinary := filepath.Join(tempDir, testBinaryName())
	cmd := exec.Command("go", "build", "-o", cliBinary, ".")
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to build CLI binary: %v", err)
	}

	// Create input file
	inputFile := filepath.Join(tempDir, "inputs.txt")
	inputs := []string{
		"user1@example.com",
		"user2@example.com",
		"test-user",
	}
	inputContent := strings.Join(inputs, "\n")
	if err := os.WriteFile(inputFile, []byte(inputContent), 0644); err != nil {
		t.Fatalf("Failed to create input file: %v", err)
	}

	outputDir := filepath.Join(tempDir, "batch-output")

	// Run batch command
	cmd = exec.Command(cliBinary, "batch", inputFile, "--output-dir", outputDir)
	if err := cmd.Run(); err != nil {
		t.Fatalf("Batch command failed: %v", err)
	}

	// Verify each generated file matches library output
	config := jdenticon.DefaultConfig()
	for _, input := range inputs {
		filename := sanitizeFilename(input) + ".svg"
		filepath := filepath.Join(outputDir, filename)

		// Read CLI-generated file
		cliContent, err := os.ReadFile(filepath)
		if err != nil {
			t.Errorf("Failed to read CLI-generated file for %s: %v", input, err)
			continue
		}

		// Generate using library
		librarySVG, err := jdenticon.ToSVGWithConfig(context.Background(), input, 200, config)
		if err != nil {
			t.Errorf("Library generation failed for %s: %v", input, err)
			continue
		}

		// Compare
		if string(cliContent) != librarySVG {
			t.Errorf("Batch file for %s differs from library output", input)
		}
	}
}

// TestCLIErrorHandling tests that CLI properly handles error cases
func TestCLIErrorHandling(t *testing.T) {
	// Build the CLI binary first
	tempDir, err := os.MkdirTemp("", "jdenticon-error-test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	cliBinary := filepath.Join(tempDir, testBinaryName())
	cmd := exec.Command("go", "build", "-o", cliBinary, ".")
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to build CLI binary: %v", err)
	}

	errorCases := []struct {
		name        string
		args        []string
		expectError bool
	}{
		{
			name:        "no arguments",
			args:        []string{},
			expectError: false, // Should show help, not error
		},
		{
			name:        "invalid format",
			args:        []string{"generate", "--format", "invalid", "test"},
			expectError: true,
		},
		{
			name:        "negative size",
			args:        []string{"generate", "--size", "-1", "test"},
			expectError: true,
		},
		{
			name:        "generate no arguments",
			args:        []string{"generate"},
			expectError: true,
		},
		{
			name:        "batch missing output dir",
			args:        []string{"batch", "somefile.txt"},
			expectError: true,
		},
		{
			name:        "batch missing input file",
			args:        []string{"batch", "nonexistent.txt", "--output-dir", "/tmp"},
			expectError: true,
		},
	}

	for _, tc := range errorCases {
		t.Run(tc.name, func(t *testing.T) {
			cmd := exec.Command(cliBinary, tc.args...)
			var output bytes.Buffer
			cmd.Stdout = &output
			cmd.Stderr = &output

			err := cmd.Run()

			if tc.expectError && err == nil {
				t.Errorf("Expected error but command succeeded. Output: %s", output.String())
			} else if !tc.expectError && err != nil {
				t.Errorf("Expected success but command failed: %v. Output: %s", err, output.String())
			}
		})
	}
}
