package commands

import (
	"strings"
	"testing"

	"c65gm/internal/compiler"
	"c65gm/internal/preproc"
)

func TestIncrCommand_WillHandle(t *testing.T) {
	tests := []struct {
		name     string
		line     string
		expected bool
	}{
		{"INC keyword", "INC myvar", true},
		{"INCREMENT keyword", "INCREMENT myvar", true},
		{"inc lowercase", "inc myvar", true},
		{"New syntax ++ literal", "myvar++", true},
		{"Invalid - space before ++", "myvar ++", false},
		{"Invalid - no params", "INC", false},
		{"Invalid - too many params old", "INC a b c", false},
		{"Invalid - wrong suffix", "myvar+-", false},
		{"Invalid - ADD command", "ADD x TO y", false},
	}

	cmd := &IncrCommand{}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}
			result := cmd.WillHandle(line)
			if result != tt.expected {
				t.Errorf("WillHandle(%q) = %v, want %v", tt.line, result, tt.expected)
			}
		})
	}
}

func TestDecrCommand_WillHandle(t *testing.T) {
	tests := []struct {
		name     string
		line     string
		expected bool
	}{
		{"DEC keyword", "DEC myvar", true},
		{"DECREMENT keyword", "DECREMENT myvar", true},
		{"dec lowercase", "dec myvar", true},
		{"New syntax -- literal", "myvar--", true},
		{"Invalid - space before --", "myvar --", false},
		{"Invalid - no params", "DEC", false},
		{"Invalid - too many params old", "DEC a b c", false},
		{"Invalid - wrong suffix", "myvar-+", false},
		{"Invalid - SUB command", "SUB x FROM y", false},
	}

	cmd := &DecrCommand{}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}
			result := cmd.WillHandle(line)
			if result != tt.expected {
				t.Errorf("WillHandle(%q) = %v, want %v", tt.line, result, tt.expected)
			}
		})
	}
}

func TestIncrCommand_InterpretAndGenerate(t *testing.T) {
	tests := []struct {
		name        string
		setup       func(*compiler.CompilerContext)
		line        string
		expectError bool
		checkAsm    func(*testing.T, []string)
	}{
		{
			name: "INC byte variable old syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "INC counter",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 1 {
					t.Errorf("Expected 1 asm line, got %d", len(asm))
					return
				}
				if !strings.Contains(asm[0], "inc counter") {
					t.Errorf("Expected 'inc counter', got %q", asm[0])
				}
			},
		},
		{
			name: "INC byte variable new syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "counter++",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 1 {
					t.Errorf("Expected 1 asm line, got %d", len(asm))
					return
				}
				if !strings.Contains(asm[0], "inc counter") {
					t.Errorf("Expected 'inc counter', got %q", asm[0])
				}
			},
		},
		{
			name: "INC word variable old syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "INCREMENT pointer",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 4 {
					t.Errorf("Expected 4 asm lines for word inc, got %d", len(asm))
					return
				}
				if !strings.Contains(asm[0], "inc pointer") {
					t.Errorf("Expected 'inc pointer' in line 0, got %q", asm[0])
				}
				if !strings.Contains(asm[1], "bne") {
					t.Errorf("Expected 'bne' in line 1, got %q", asm[1])
				}
				if !strings.Contains(asm[2], "inc pointer+1") {
					t.Errorf("Expected 'inc pointer+1' in line 2, got %q", asm[2])
				}
				// Line 3 should be the label
			},
		},
		{
			name: "INC word variable new syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "pointer++",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 4 {
					t.Errorf("Expected 4 asm lines for word inc, got %d", len(asm))
				}
			},
		},
		{
			name: "INC absolute address",
			setup: func(ctx *compiler.CompilerContext) {
				// No variables needed
			},
			line:        "INC $D020",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 1 {
					t.Errorf("Expected 1 asm line, got %d", len(asm))
					return
				}
				if !strings.Contains(strings.ToLower(asm[0]), "inc $d020") {
					t.Errorf("Expected 'inc $d020', got %q", asm[0])
				}
			},
		},

		{
			name: "Error: INC unknown variable",
			setup: func(ctx *compiler.CompilerContext) {
				// No setup
			},
			line:        "INC unknown",
			expectError: true,
		},
		{
			name: "Error: INC constant variable",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "INC MAX",
			expectError: true,
		},
		{
			name: "Error: new syntax on unknown variable",
			setup: func(ctx *compiler.CompilerContext) {
				// No setup
			},
			line:        "unknown++",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := compiler.NewCompilerContext(preproc.NewPragma())
			if tt.setup != nil {
				tt.setup(ctx)
			}

			cmd := &IncrCommand{}
			line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}

			err := cmd.Interpret(line, ctx)
			if tt.expectError {
				if err == nil {
					t.Errorf("Expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			asm, err := cmd.Generate(ctx)
			if err != nil {
				t.Fatalf("Generate error: %v", err)
			}

			if tt.checkAsm != nil {
				tt.checkAsm(t, asm)
			}
		})
	}
}

func TestDecrCommand_InterpretAndGenerate(t *testing.T) {
	tests := []struct {
		name        string
		setup       func(*compiler.CompilerContext)
		line        string
		expectError bool
		checkAsm    func(*testing.T, []string)
	}{
		{
			name: "DEC byte variable old syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "DEC counter",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 1 {
					t.Errorf("Expected 1 asm line, got %d", len(asm))
					return
				}
				if !strings.Contains(asm[0], "dec counter") {
					t.Errorf("Expected 'dec counter', got %q", asm[0])
				}
			},
		},
		{
			name: "DEC byte variable new syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "counter--",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 1 {
					t.Errorf("Expected 1 asm line, got %d", len(asm))
					return
				}
				if !strings.Contains(asm[0], "dec counter") {
					t.Errorf("Expected 'dec counter', got %q", asm[0])
				}
			},
		},
		{
			name: "DEC word variable old syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "DECREMENT pointer",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 5 {
					t.Errorf("Expected 5 asm lines for word dec, got %d", len(asm))
					return
				}
				if !strings.Contains(asm[0], "lda pointer") {
					t.Errorf("Expected 'lda pointer' in line 0, got %q", asm[0])
				}
				if !strings.Contains(asm[1], "bne") {
					t.Errorf("Expected 'bne' in line 1, got %q", asm[1])
				}
				if !strings.Contains(asm[2], "dec pointer+1") {
					t.Errorf("Expected 'dec pointer+1' in line 2, got %q", asm[2])
				}
				// Line 3 is the label
				if !strings.Contains(asm[4], "dec pointer") {
					t.Errorf("Expected 'dec pointer' in line 4, got %q", asm[4])
				}
			},
		},
		{
			name: "DEC word variable new syntax",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "pointer--",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 5 {
					t.Errorf("Expected 5 asm lines for word dec, got %d", len(asm))
				}
			},
		},
		{
			name: "DEC absolute address",
			setup: func(ctx *compiler.CompilerContext) {
				// No variables needed
			},
			line:        "DEC $D020",
			expectError: false,
			checkAsm: func(t *testing.T, asm []string) {
				if len(asm) != 1 {
					t.Errorf("Expected 1 asm line, got %d", len(asm))
					return
				}
				if !strings.Contains(strings.ToLower(asm[0]), "dec $d020") {
					t.Errorf("Expected 'dec $d020', got %q", asm[0])
				}
			},
		},
		{
			name: "Error: DEC unknown variable",
			setup: func(ctx *compiler.CompilerContext) {
				// No setup
			},
			line:        "DEC unknown",
			expectError: true,
		},
		{
			name: "Error: DEC constant variable",
			setup: func(ctx *compiler.CompilerContext) {
				ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			line:        "DEC MAX",
			expectError: true,
		},
		{
			name: "Error: new syntax on unknown variable",
			setup: func(ctx *compiler.CompilerContext) {
				// No setup
			},
			line:        "unknown--",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := compiler.NewCompilerContext(preproc.NewPragma())
			if tt.setup != nil {
				tt.setup(ctx)
			}

			cmd := &DecrCommand{}
			line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}

			err := cmd.Interpret(line, ctx)
			if tt.expectError {
				if err == nil {
					t.Errorf("Expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			asm, err := cmd.Generate(ctx)
			if err != nil {
				t.Fatalf("Generate error: %v", err)
			}

			if tt.checkAsm != nil {
				tt.checkAsm(t, asm)
			}
		})
	}
}

func TestIncrDecrCommand_FullNameResolution(t *testing.T) {
	// Test that variable name resolution works with full names
	ctx := compiler.NewCompilerContext(preproc.NewPragma())

	// Add a variable with scoped name directly
	ctx.SymbolTable.AddVar("counter", "myfunc", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	// Note: CurrentScope will return nil (global) since we're not in a function context
	// The symbol table lookup will try scoped search and fall back to global

	// Test that using the base name in global scope won't find the scoped var
	incrCmd := &IncrCommand{}
	line := preproc.Line{Text: "INC counter", LineNo: 1, Filename: "test.c65"}
	err := incrCmd.Interpret(line, ctx)

	// Should fail - counter exists only in myfunc scope, not global
	if err == nil {
		t.Errorf("Expected error when accessing scoped variable from global scope")
	}
}
