package main

import (
	"fmt"
	"path/filepath"
	"strings"
	"testing"
)

// TestParseArgs tests the flexible argument parsing logic
func TestParseArgs(t *testing.T) {
	tests := []struct {
		name           string
		args           []string
		wantInput      string
		wantOutput     string
		wantIsBuild    bool
		wantKeepAsm    bool
		wantNoCBM      bool
		expectError    bool
	}{
		// Simple file argument (default build mode)
		{
			name:        "simple file argument",
			args:        []string{"program.c65"},
			wantInput:   "program.c65",
			wantOutput:  "program.prg",
			wantIsBuild: true,
		},
		// Short -i flag
		{
			name:        "short -i flag",
			args:        []string{"-i", "program.c65"},
			wantInput:   "program.c65",
			wantOutput:  "program.prg",
			wantIsBuild: true,
		},
		// Long -in flag (legacy)
		{
			name:        "long -in flag",
			args:        []string{"-in", "program.c65"},
			wantInput:   "program.c65",
			wantOutput:  "program.prg",
			wantIsBuild: true,
		},
		// With output .prg (build mode)
		{
			name:        "with output .prg",
			args:        []string{"program.c65", "-o", "game.prg"},
			wantInput:   "program.c65",
			wantOutput:  "game.prg",
			wantIsBuild: true,
		},
		// With output .asm (compile mode)
		{
			name:        "with output .asm",
			args:        []string{"program.c65", "-o", "output.asm"},
			wantInput:   "program.c65",
			wantOutput:  "output.asm",
			wantIsBuild: false,
		},
		// With -out flag (legacy)
		{
			name:        "with -out flag",
			args:        []string{"program.c65", "-out", "output.asm"},
			wantInput:   "program.c65",
			wantOutput:  "output.asm",
			wantIsBuild: false,
		},
		// With keep-asm flag
		{
			name:        "with keep-asm flag",
			args:        []string{"program.c65", "--keep-asm"},
			wantInput:   "program.c65",
			wantOutput:  "program.prg",
			wantIsBuild: true,
			wantKeepAsm: true,
		},
		// With no-cbm flag
		{
			name:        "with no-cbm flag",
			args:        []string{"program.c65", "--no-cbm"},
			wantInput:   "program.c65",
			wantOutput:  "program.prg",
			wantIsBuild: true,
			wantNoCBM:   true,
		},
		// Mixed flags
		{
			name:        "mixed flags",
			args:        []string{"-i", "program.c65", "-o", "out.prg", "--keep-asm", "--no-cbm"},
			wantInput:   "program.c65",
			wantOutput:  "out.prg",
			wantIsBuild: true,
			wantKeepAsm: true,
			wantNoCBM:   true,
		},
		// No input file (error)
		{
			name:        "no input file",
			args:        []string{"-o", "output.prg"},
			expectError: true,
		},
		// Empty args (error)
		{
			name:        "empty args",
			args:        []string{},
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			input, output, isBuild, keepAsm, noCBM, err := parseArgs(tt.args)
			
			if tt.expectError {
				if err == nil {
					t.Errorf("parseArgs() expected error, got nil")
				}
				return
			}
			
			if err != nil {
				t.Errorf("parseArgs() unexpected error: %v", err)
				return
			}
			
			if input != tt.wantInput {
				t.Errorf("parseArgs() input = %v, want %v", input, tt.wantInput)
			}
			
			if output != tt.wantOutput {
				t.Errorf("parseArgs() output = %v, want %v", output, tt.wantOutput)
			}
			
			if isBuild != tt.wantIsBuild {
				t.Errorf("parseArgs() isBuild = %v, want %v", isBuild, tt.wantIsBuild)
			}
			
			if keepAsm != tt.wantKeepAsm {
				t.Errorf("parseArgs() keepAsm = %v, want %v", keepAsm, tt.wantKeepAsm)
			}
			
			if noCBM != tt.wantNoCBM {
				t.Errorf("parseArgs() noCBM = %v, want %v", noCBM, tt.wantNoCBM)
			}
		})
	}
}

// TestOutputExtensionDetection tests detection of build vs compile mode by output extension
func TestOutputExtensionDetection(t *testing.T) {
	tests := []struct {
		name       string
		outputFile string
		wantIsBuild bool
	}{
		{".prg extension", "program.prg", true},
		{".PRG uppercase", "program.PRG", true},
		{".asm extension", "program.asm", false},
		{".ASM uppercase", "program.ASM", false},
		{".s extension", "program.s", false},
		{"no extension", "program", false}, // No .prg extension = compile mode
		{".bin extension", "program.bin", false}, // No .prg extension = compile mode
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			isBuild := strings.HasSuffix(strings.ToLower(tt.outputFile), ".prg")
			if isBuild != tt.wantIsBuild {
				t.Errorf("isBuild(%q) = %v, want %v", tt.outputFile, isBuild, tt.wantIsBuild)
			}
		})
	}
}

// TestDefaultOutputNaming tests default output filename generation
func TestDefaultOutputNaming(t *testing.T) {
	tests := []struct {
		name      string
		inputFile string
		isBuild   bool
		wantOutput string
	}{
		{"build mode .c65", "program.c65", true, "program.prg"},
		{"compile mode .c65", "program.c65", false, "program.asm"},
		{"build mode .C65 uppercase", "program.C65", true, "program.prg"},
		{"no extension", "program", true, "program.prg"},
		{"multiple dots", "my.program.c65", true, "my.program.prg"},
		{"path with extension", "/path/to/program.c65", true, "program.prg"},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			base := strings.TrimSuffix(filepath.Base(tt.inputFile), filepath.Ext(tt.inputFile))
			var output string
			if tt.isBuild {
				output = base + ".prg"
			} else {
				output = base + ".asm"
			}
			
			if output != tt.wantOutput {
				t.Errorf("defaultOutput(%q, %v) = %v, want %v", tt.inputFile, tt.isBuild, output, tt.wantOutput)
			}
		})
	}
}

// TestSubcommandParsing tests explicit subcommand parsing
func TestSubcommandParsing(t *testing.T) {
	tests := []struct {
		name        string
		args        []string
		wantCommand string
		expectError bool
	}{
		{"build command", []string{"build", "-i", "program.c65"}, "build", false},
		{"compile command", []string{"compile", "-i", "program.c65"}, "compile", false},
		{"help command", []string{"help"}, "help", false},
		{"unknown command", []string{"unknown", "program.c65"}, "", true},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if len(tt.args) == 0 {
				t.Skip("empty args")
			}
			
			cmd := tt.args[0]
			isSubcommand := cmd == "build" || cmd == "compile" || cmd == "help"
			
			if tt.expectError && isSubcommand {
				t.Errorf("Expected error for command %q but it's a valid subcommand", cmd)
			}
			
			if !tt.expectError && cmd != tt.wantCommand {
				t.Errorf("Command = %q, want %q", cmd, tt.wantCommand)
			}
		})
	}
}

// TestACMECommandBuilding tests building the ACME command line arguments
func TestACMECommandBuilding(t *testing.T) {
	tests := []struct {
		name     string
		output   string
		asmFile  string
		noCBM    bool
		wantArgs []string
	}{
		{
			name:     "default with cbm",
			output:   "program.prg",
			asmFile:  "temp.asm",
			noCBM:    false,
			wantArgs: []string{"-o", "program.prg", "-f", "cbm", "temp.asm"},
		},
		{
			name:     "with no-cbm flag",
			output:   "program.prg",
			asmFile:  "temp.asm",
			noCBM:    true,
			wantArgs: []string{"-o", "program.prg", "temp.asm"},
		},
		{
			name:     "different output name",
			output:   "game.prg",
			asmFile:  "temp.asm",
			noCBM:    false,
			wantArgs: []string{"-o", "game.prg", "-f", "cbm", "temp.asm"},
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			args := buildACMEArgs(tt.output, tt.asmFile, tt.noCBM)
			
			if len(args) != len(tt.wantArgs) {
				t.Errorf("buildACMEArgs() length = %v, want %v", len(args), len(tt.wantArgs))
				return
			}
			
			for i := range args {
				if args[i] != tt.wantArgs[i] {
					t.Errorf("buildACMEArgs()[%d] = %v, want %v", i, args[i], tt.wantArgs[i])
				}
			}
		})
	}
}

// TestACMEAvailableError tests the ACME availability error message
func TestACMEAvailableError(t *testing.T) {
	// We can't easily test the actual exec.LookPath call without mocking,
	// but we can test that the function returns an error with helpful message
	// when ACME is not found.
	
	// This is a simple test to ensure the function signature and basic logic
	err := checkACMEAvailable()
	// We can't assert much here without mocking, but we can verify the function exists
	if err != nil && !strings.Contains(err.Error(), "ACME") {
		t.Errorf("checkACMEAvailable() error should mention ACME, got: %v", err)
	}
}

// TestBackwardCompatibility tests legacy flag support
func TestBackwardCompatibility(t *testing.T) {
	tests := []struct {
		name        string
		args        []string
		wantInput   string
		wantOutput  string
		description string
	}{
		{
			name:       "legacy -in -out flags",
			args:       []string{"-in", "program.c65", "-out", "output.asm"},
			wantInput:  "program.c65",
			wantOutput: "output.asm",
			description: "Traditional c65gm syntax",
		},
		{
			name:       "legacy -in only",
			args:       []string{"-in", "program.c65"},
			wantInput:  "program.c65",
			wantOutput: "program.asm",
			description: "Legacy mode with default output",
		},
		{
			name:       "mixed legacy and new",
			args:       []string{"-in", "program.c65", "-o", "game.prg"},
			wantInput:  "program.c65",
			wantOutput: "game.prg",
			description: "Mixing -in with -o",
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			input, output, _, _, _, err := parseArgs(tt.args)
			if err != nil && !strings.Contains(err.Error(), "subcommand") {
				// parseArgs treats legacy flags as regular flags, which is correct
				if input != tt.wantInput {
					t.Errorf("parseArgs() input = %v, want %v", input, tt.wantInput)
				}
				if output != tt.wantOutput {
					t.Errorf("parseArgs() output = %v, want %v", output, tt.wantOutput)
				}
			}
		})
	}
}

// TestEdgeCases tests various edge cases in argument parsing
func TestEdgeCases(t *testing.T) {
	tests := []struct {
		name        string
		args        []string
		expectError bool
		description string
	}{
		{
			name:        "i flag without value",
			args:        []string{"-i"},
			expectError: true,
			description: "-i flag without filename should error",
		},
		{
			name:        "in flag without value",
			args:        []string{"-in"},
			expectError: true,
			description: "-in flag without filename should error",
		},
		{
			name:        "out flag without value",
			args:        []string{"program.c65", "-o"},
			expectError: true,
			description: "-o flag without filename should error",
		},
		{
			name:        "multiple input files",
			args:        []string{"file1.c65", "file2.c65", "-o", "output.prg"},
			expectError: false,
			description: "First non-flag is input, second is ignored",
		},
		{
			name:        "unknown flag",
			args:        []string{"program.c65", "--unknown-flag"},
			expectError: false,
			description: "Unknown flags are ignored (treated as positional)",
		},
		{
			name:        "help flag with other args",
			args:        []string{"-h", "program.c65"},
			expectError: true,
			description: "-h flag should trigger help",
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, _, _, _, _, err := parseArgs(tt.args)
			
			if tt.expectError && err == nil {
				t.Errorf("parseArgs() expected error for %s", tt.description)
			} else if !tt.expectError && err != nil && !strings.Contains(err.Error(), "subcommand") {
				t.Errorf("parseArgs() unexpected error for %s: %v", tt.description, err)
			}
		})
	}
}

// TestFlagEquivalence tests that -i/-in and -o/-out are equivalent
func TestFlagEquivalence(t *testing.T) {
	testCases := []struct {
		args1 []string
		args2 []string
	}{
		{
			[]string{"-i", "program.c65"},
			[]string{"-in", "program.c65"},
		},
		{
			[]string{"program.c65", "-o", "output.prg"},
			[]string{"program.c65", "-out", "output.prg"},
		},
		{
			[]string{"-i", "program.c65", "-o", "output.asm"},
			[]string{"-in", "program.c65", "-out", "output.asm"},
		},
	}
	
	for i, tc := range testCases {
		t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
			input1, output1, isBuild1, keepAsm1, noCBM1, err1 := parseArgs(tc.args1)
			input2, output2, isBuild2, keepAsm2, noCBM2, err2 := parseArgs(tc.args2)
			
			// Both should succeed or both should fail
			if (err1 == nil) != (err2 == nil) {
				t.Errorf("parseArgs() inconsistent errors: args1=%v err=%v, args2=%v err=%v",
					tc.args1, err1, tc.args2, err2)
				return
			}
			
			if err1 != nil {
				return // Both failed, that's OK for this test
			}
			
			// Check equivalence
			if input1 != input2 {
				t.Errorf("input mismatch: %q != %q", input1, input2)
			}
			if output1 != output2 {
				t.Errorf("output mismatch: %q != %q", output1, output2)
			}
			if isBuild1 != isBuild2 {
				t.Errorf("isBuild mismatch: %v != %v", isBuild1, isBuild2)
			}
			if keepAsm1 != keepAsm2 {
				t.Errorf("keepAsm mismatch: %v != %v", keepAsm1, keepAsm2)
			}
			if noCBM1 != noCBM2 {
				t.Errorf("noCBM mismatch: %v != %v", noCBM1, noCBM2)
			}
		})
	}
}

// buildACMEArgs is a test helper that extracts ACME argument building logic
func buildACMEArgs(outputFile, asmFile string, noCBM bool) []string {
	args := []string{"-o", outputFile}
	if !noCBM {
		args = append(args, "-f", "cbm")
	}
	args = append(args, asmFile)
	return args
}

// parseArgs is a test helper that extracts the parsing logic from main()
func parseArgs(args []string) (inputFile, outputFile string, isBuild, keepAsm, noCBM bool, err error) {
	if len(args) == 0 {
		return "", "", false, false, false, fmt.Errorf("no arguments")
	}
	
	// Check for help flags
	if args[0] == "-h" || args[0] == "--help" {
		return "", "", false, false, false, fmt.Errorf("help requested")
	}
	
	// Check if first arg is a subcommand
	if len(args) > 0 && (args[0] == "build" || args[0] == "compile" || args[0] == "help") {
		// For testing purposes, we'll handle subcommands differently
		// In real code, these are handled by runBuildCommand/runCompileCommand
		return "", "", false, false, false, fmt.Errorf("subcommand not handled in test")
	}
	
	// Default mode parsing (from main())
	for i := 0; i < len(args); i++ {
		arg := args[i]
		
		if arg == "-i" || arg == "-in" {
			if i+1 < len(args) {
				inputFile = args[i+1]
				i++ // Skip next arg
			} else {
				return "", "", false, false, false, fmt.Errorf("-i flag requires a filename")
			}
		} else if arg == "-o" || arg == "-out" {
			if i+1 < len(args) {
				outputFile = args[i+1]
				i++ // Skip next arg
			} else {
				return "", "", false, false, false, fmt.Errorf("-o flag requires a filename")
			}
		} else if arg == "--keep-asm" {
			keepAsm = true
		} else if arg == "--no-cbm" {
			noCBM = true
		} else if !strings.HasPrefix(arg, "-") && inputFile == "" {
			// First non-flag argument is the input file
			inputFile = arg
		}
	}
	
	if inputFile == "" {
		return "", "", false, false, false, fmt.Errorf("no input file specified")
	}
	
	// Default output based on input
	if outputFile == "" {
		base := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile))
		outputFile = base + ".prg" // Default to build mode (.prg)
	}
	
	// Determine mode by output extension
	isBuild = strings.HasSuffix(strings.ToLower(outputFile), ".prg")
	
	return inputFile, outputFile, isBuild, keepAsm, noCBM, nil
}