package commands

import (
	"strings"
	"testing"

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

func TestWordCommand_WillHandle(t *testing.T) {
	tests := []struct {
		name string
		text string
		want bool
	}{
		{
			name: "handles WORD",
			text: "WORD x",
			want: true,
		},
		{
			name: "handles word lowercase",
			text: "word x",
			want: true,
		},
		{
			name: "handles WORD with init",
			text: "WORD x = 1000",
			want: true,
		},
		{
			name: "handles WORD with string",
			text: `WORD ptr = "hello"`,
			want: true,
		},
		{
			name: "handles WORD at absolute",
			text: "WORD x @ $C000",
			want: true,
		},
		{
			name: "handles WORD CONST",
			text: "WORD CONST x = 1000",
			want: true,
		},
		{
			name: "does not handle BYTE",
			text: "BYTE x",
			want: false,
		},
		{
			name: "does not handle empty",
			text: "",
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cmd := &WordCommand{}
			line := preproc.Line{Text: tt.text}
			got := cmd.WillHandle(line)
			if got != tt.want {
				t.Errorf("WillHandle() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestWordCommand_Interpret(t *testing.T) {
	tests := []struct {
		name        string
		text        string
		wantErr     bool
		errContains string
		checkVar    func(*testing.T, *compiler.CompilerContext)
	}{
		{
			name:    "simple word",
			text:    "WORD x",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("x", nil)
				if sym == nil {
					t.Fatal("Expected variable x to be declared")
				}
				if !sym.IsWord() {
					t.Error("Expected word variable")
				}
				if sym.IsConst() {
					t.Error("Expected regular variable, not const")
				}
				if sym.Value != 0 {
					t.Errorf("Expected init value 0, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with decimal init",
			text:    "WORD counter = 1000",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("counter", nil)
				if sym == nil {
					t.Fatal("Expected variable counter to be declared")
				}
				if sym.Value != 1000 {
					t.Errorf("Expected init value 1000, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with hex init",
			text:    "WORD status = $FFFF",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("status", nil)
				if sym == nil {
					t.Fatal("Expected variable status to be declared")
				}
				if sym.Value != 65535 {
					t.Errorf("Expected init value 65535, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with string pointer",
			text:    `WORD msg = "hello world"`,
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("msg", nil)
				if sym == nil {
					t.Fatal("Expected variable msg to be declared")
				}
				if !sym.Has(compiler.FlagLabelRef) {
					t.Error("Expected label reference")
				}
				if sym.LabelRef == "" {
					t.Error("Expected non-empty label reference")
				}
				// Check that string was added to handler
				strs := ctx.ConstStrHandler.GenerateConstStrDecls()
				if len(strs) == 0 {
					t.Error("Expected string to be added to ConstStrHandler")
				}
			},
		},
		{
			name:    "word at absolute address",
			text:    "WORD ptr @ $C000",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("ptr", nil)
				if sym == nil {
					t.Fatal("Expected variable ptr to be declared")
				}
				if !sym.IsAbsolute() {
					t.Error("Expected absolute variable")
				}
				if sym.AbsAddr != 0xC000 {
					t.Errorf("Expected address $C000, got $%04X", sym.AbsAddr)
				}
			},
		},
		{
			name:    "word at zero page",
			text:    "WORD zpvar @ $80",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("zpvar", nil)
				if sym == nil {
					t.Fatal("Expected variable zpvar to be declared")
				}
				if !sym.IsZeroPage() {
					t.Error("Expected zero page variable")
				}
			},
		},
		{
			name:    "const word",
			text:    "WORD CONST maxval = 65535",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("maxval", nil)
				if sym == nil {
					t.Fatal("Expected constant maxval to be declared")
				}
				if !sym.IsConst() {
					t.Error("Expected constant")
				}
				if sym.Value != 65535 {
					t.Errorf("Expected value 65535, got %d", sym.Value)
				}
			},
		},
		{
			name:    "const word with hex",
			text:    "WORD CONST flag = $FFFF",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("flag", nil)
				if sym == nil {
					t.Fatal("Expected constant flag to be declared")
				}
				if !sym.IsConst() {
					t.Error("Expected constant")
				}
				if sym.Value != 65535 {
					t.Errorf("Expected value 65535, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with expression",
			text:    "WORD x = 100+200",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("x", nil)
				if sym == nil {
					t.Fatal("Expected variable x to be declared")
				}
				if sym.Value != 300 {
					t.Errorf("Expected value 300, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with binary",
			text:    "WORD x = %1111111111111111",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("x", nil)
				if sym == nil {
					t.Fatal("Expected variable x to be declared")
				}
				if sym.Value != 65535 {
					t.Errorf("Expected value 65535, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with bitwise OR",
			text:    "WORD x = $FF00|$00FF",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("x", nil)
				if sym == nil {
					t.Fatal("Expected variable x to be declared")
				}
				if sym.Value != 65535 {
					t.Errorf("Expected value 65535, got %d", sym.Value)
				}
			},
		},
		{
			name:    "word with bitwise AND",
			text:    "WORD x = $FFFF&$00FF",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("x", nil)
				if sym == nil {
					t.Fatal("Expected variable x to be declared")
				}
				if sym.Value != 255 {
					t.Errorf("Expected value 255, got %d", sym.Value)
				}
			},
		},
		{
			name:        "word with out of range value",
			text:        "WORD x = 65536",
			wantErr:     true,
			errContains: "out of range",
		},
		{
			name:        "const word out of range",
			text:        "WORD CONST x = 65536",
			wantErr:     true,
			errContains: "out of range",
		},
		{
			name:        "word without name",
			text:        "WORD",
			wantErr:     true,
			errContains: "wrong number of parameters",
		},
		{
			name:        "word with invalid identifier",
			text:        "WORD 123invalid",
			wantErr:     true,
			errContains: "invalid identifier",
		},
		{
			name:        "word with wrong operator",
			text:        "WORD x + 10",
			wantErr:     true,
			errContains: "expected '=' or '@'",
		},
		{
			name:        "word string with wrong operator",
			text:        `WORD x @ "hello"`,
			wantErr:     true,
			errContains: "expected '=' when assigning string pointer",
		},
		{
			name:        "const without equals",
			text:        "WORD CONST x 10",
			wantErr:     true,
			errContains: "expected '='",
		},
		{
			name:        "wrong keyword instead of CONST",
			text:        "WORD VAR x = 10",
			wantErr:     true,
			errContains: "expected CONST keyword",
		},
		{
			name:        "duplicate declaration",
			text:        "WORD x",
			wantErr:     true,
			errContains: "already declared",
		},
	}

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

			// For duplicate test, pre-declare the variable
			if tt.name == "duplicate declaration" {
				ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			}

			cmd := &WordCommand{}
			line := preproc.Line{
				Text:           tt.text,
				Filename:       "test.c65",
				LineNo:         1,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

			err := cmd.Interpret(line, ctx)

			if tt.wantErr {
				if err == nil {
					t.Fatal("Expected error, got nil")
				}
				if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
					t.Errorf("Error %q does not contain %q", err.Error(), tt.errContains)
				}
			} else {
				if err != nil {
					t.Fatalf("Unexpected error: %v", err)
				}
				if tt.checkVar != nil {
					tt.checkVar(t, ctx)
				}
			}
		})
	}
}

func TestWordCommand_Generate(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	cmd := &WordCommand{}
	line := preproc.Line{
		Text:           "WORD x = 1000",
		Filename:       "test.c65",
		LineNo:         1,
		Kind:           preproc.Source,
		PragmaSetIndex: 0,
	}

	// Interpret first
	if err := cmd.Interpret(line, ctx); err != nil {
		t.Fatalf("Interpret failed: %v", err)
	}

	// Generate should return nil (variables handled by assembleOutput)
	output, err := cmd.Generate(ctx)
	if err != nil {
		t.Errorf("Generate returned error: %v", err)
	}
	if output != nil {
		t.Errorf("Generate should return nil, got %v", output)
	}
}

func TestWordCommand_WithConstantExpression(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	// First, declare a constant
	ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
	ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindWord, 500, preproc.Line{Filename: "test.c65", LineNo: 1})

	// Now declare a word using the constant in an expression
	cmd := &WordCommand{}
	line := preproc.Line{
		Text:           "WORD x = MAXVAL+OFFSET",
		Filename:       "test.c65",
		LineNo:         1,
		Kind:           preproc.Source,
		PragmaSetIndex: 0,
	}

	if err := cmd.Interpret(line, ctx); err != nil {
		t.Fatalf("Interpret failed: %v", err)
	}

	// Check value
	sym := ctx.SymbolTable.Lookup("x", nil)
	if sym == nil {
		t.Fatal("Expected variable x to be declared")
	}
	if sym.Value != 1500 {
		t.Errorf("Expected value 1500 (1000+500), got %d", sym.Value)
	}
}

func TestWordCommand_LabelReference(t *testing.T) {
	tests := []struct {
		name     string
		text     string
		wantErr  bool
		checkVar func(*testing.T, *compiler.CompilerContext)
	}{
		{
			name:    "word with label reference",
			text:    "WORD ptr = @myLabel",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("ptr", nil)
				if sym == nil {
					t.Fatal("Expected variable ptr to be declared")
				}
				if !sym.Has(compiler.FlagLabelRef) {
					t.Error("Expected label reference flag")
				}
				if sym.LabelRef != "myLabel" {
					t.Errorf("Expected LabelRef 'myLabel', got %q", sym.LabelRef)
				}
			},
		},
		{
			name:    "word referencing another variable",
			text:    "WORD ptr = @existingVar",
			wantErr: false,
			checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
				sym := ctx.SymbolTable.Lookup("ptr", nil)
				if sym == nil {
					t.Fatal("Expected variable ptr to be declared")
				}
				if !sym.Has(compiler.FlagLabelRef) {
					t.Error("Expected label reference flag")
				}
				// Should expand to the full name of existingVar
				if sym.LabelRef != "existingVar" {
					t.Errorf("Expected LabelRef 'existingVar', got %q", sym.LabelRef)
				}
			},
		},
	}

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

			// Pre-declare existingVar for the reference test
			if tt.name == "word referencing another variable" {
				ctx.SymbolTable.AddVar("existingVar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			}

			cmd := &WordCommand{}
			line := preproc.Line{
				Text:           tt.text,
				Filename:       "test.c65",
				LineNo:         1,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

			err := cmd.Interpret(line, ctx)

			if tt.wantErr {
				if err == nil {
					t.Fatal("Expected error, got nil")
				}
			} else {
				if err != nil {
					t.Fatalf("Unexpected error: %v", err)
				}
				if tt.checkVar != nil {
					tt.checkVar(t, ctx)
				}
			}
		})
	}
}

func TestWordCommand_LabelReferenceLocalExpansion(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	// Enter a function scope
	_, err := ctx.FunctionHandler.HandleFuncDecl(preproc.Line{
		Text:     "FUNC myFunc ( {BYTE dummy} )",
		Filename: "test.c65",
		LineNo:   1,
		Kind:     preproc.Source,
	})
	if err != nil {
		t.Fatalf("HandleFuncDecl failed: %v", err)
	}

	// Verify we're in function scope
	if ctx.FunctionHandler.CurrentFunction() != "myFunc" {
		t.Fatalf("Expected current function 'myFunc', got %q", ctx.FunctionHandler.CurrentFunction())
	}

	// Declare a local variable in function scope
	ctx.SymbolTable.AddVar("localBuffer", "myFunc", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	// Now declare another variable referencing the local
	cmd := &WordCommand{}
	line := preproc.Line{
		Text:           "WORD ptr = @localBuffer",
		Filename:       "test.c65",
		LineNo:         2,
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

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

	// Check that LabelRef was expanded to the full name
	sym := ctx.SymbolTable.Lookup("ptr", []string{"myFunc"})
	if sym == nil {
		t.Fatal("Expected variable ptr to be declared")
	}
	if !sym.Has(compiler.FlagLabelRef) {
		t.Error("Expected label reference flag")
	}
	// Should be expanded to "myFunc_localBuffer"
	if sym.LabelRef != "myFunc_localBuffer" {
		t.Errorf("Expected LabelRef 'myFunc_localBuffer', got %q", sym.LabelRef)
	}
}

func TestWordCommand_LabelReferenceErrors(t *testing.T) {
	tests := []struct {
		name        string
		text        string
		errContains string
	}{
		{
			name:        "label ref with wrong operator",
			text:        "WORD x @ @myLabel",
			errContains: "expected '=' when assigning label reference",
		},
		{
			name:        "invalid label identifier",
			text:        "WORD x = @123invalid",
			errContains: "invalid label identifier",
		},
	}

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

			cmd := &WordCommand{}
			line := preproc.Line{
				Text:           tt.text,
				Filename:       "test.c65",
				LineNo:         1,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

			err := cmd.Interpret(line, ctx)
			if err == nil {
				t.Fatal("Expected error, got nil")
			}
			if !strings.Contains(err.Error(), tt.errContains) {
				t.Errorf("Error %q does not contain %q", err.Error(), tt.errContains)
			}
		})
	}
}

func TestWordCommand_MultipleStrings(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	// Create multiple string pointers
	stringsd := []string{
		`WORD msg1 = "hello"`,
		`WORD msg2 = "world"`,
		`WORD msg3 = "test"`,
	}

	for i, text := range stringsd {
		cmd := &WordCommand{}
		line := preproc.Line{
			Text:           text,
			Filename:       "test.c65",
			LineNo:         i + 1,
			Kind:           preproc.Source,
			PragmaSetIndex: 0,
		}

		if err := cmd.Interpret(line, ctx); err != nil {
			t.Fatalf("Failed to interpret line %d: %v", i+1, err)
		}
	}

	// Check all were added
	if ctx.SymbolTable.Count() != 3 {
		t.Errorf("Expected 3 variables, got %d", ctx.SymbolTable.Count())
	}

	// Check string declarations were generated
	strDecls := ctx.ConstStrHandler.GenerateConstStrDecls()
	if len(strDecls) < 9 { // Each string needs at least 3 lines (label, data, terminator)
		t.Errorf("Expected at least 9 lines of string declarations, got %d", len(strDecls))
	}
}

func TestWordCommand_RegisterError(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	cmd := &WordCommand{}
	line := preproc.Line{
		Text:           "WORD REGISTER x",
		Filename:       "test.c65",
		LineNo:         1,
		Kind:           preproc.Source,
		PragmaSetIndex: 0,
	}

	err := cmd.Interpret(line, ctx)
	if err == nil {
		t.Fatal("Expected error for WORD REGISTER")
	}
	if !strings.Contains(err.Error(), "REGISTER") {
		t.Errorf("Error should mention REGISTER, got: %v", err)
	}
}
