package commands

import (
	"strings"
	"testing"

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

func TestByteCommand_WillHandle(t *testing.T) {
	tests := []struct {
		name string
		text string
		want bool
	}{
		{
			name: "handles BYTE",
			text: "BYTE x",
			want: true,
		},
		{
			name: "handles byte lowercase",
			text: "byte x",
			want: true,
		},
		{
			name: "handles BYTE with init",
			text: "BYTE x = 10",
			want: true,
		},
		{
			name: "handles BYTE at absolute",
			text: "BYTE x @ $C000",
			want: true,
		},
		{
			name: "handles BYTE CONST",
			text: "BYTE CONST x = 10",
			want: true,
		},
		{
			name: "does not handle WORD",
			text: "WORD x",
			want: false,
		},
		{
			name: "does not handle empty",
			text: "",
			want: false,
		},
		{
			name: "does not handle other command",
			text: "LET x 5",
			want: false,
		},
	}

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

func TestByteCommand_Interpret(t *testing.T) {
	tests := []struct {
		name        string
		text        string
		wantErr     bool
		errContains string
		checkVar    func(*testing.T, *compiler.CompilerContext)
	}{
		{
			name:    "simple byte",
			text:    "BYTE 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.IsByte() {
					t.Error("Expected byte 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:    "byte with decimal init",
			text:    "BYTE counter = 42",
			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 != 42 {
					t.Errorf("Expected init value 42, got %d", sym.Value)
				}
			},
		},
		{
			name:    "byte with hex init",
			text:    "BYTE status = $FF",
			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 != 255 {
					t.Errorf("Expected init value 255, got %d", sym.Value)
				}
			},
		},
		{
			name:    "byte at absolute address",
			text:    "BYTE 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:    "byte at zero page",
			text:    "BYTE 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 byte",
			text:    "BYTE CONST maxval = 255",
			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 != 255 {
					t.Errorf("Expected value 255, got %d", sym.Value)
				}
			},
		},
		{
			name:    "const byte with hex",
			text:    "BYTE CONST flag = $FF",
			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 != 255 {
					t.Errorf("Expected value 255, got %d", sym.Value)
				}
			},
		},
		{
			name:    "byte with expression",
			text:    "BYTE x = 10+20",
			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 != 30 {
					t.Errorf("Expected value 30, got %d", sym.Value)
				}
			},
		},
		{
			name:    "byte with binary",
			text:    "BYTE x = %11111111",
			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:    "byte with bitwise OR",
			text:    "BYTE x = $F0|$0F",
			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:    "byte with bitwise AND",
			text:    "BYTE x = $FF&$0F",
			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 != 15 {
					t.Errorf("Expected value 15, got %d", sym.Value)
				}
			},
		},
		{
			name:        "byte with out of range value",
			text:        "BYTE x = 256",
			wantErr:     true,
			errContains: "out of range",
		},
		{
			name:        "const byte out of range",
			text:        "BYTE CONST x = 256",
			wantErr:     true,
			errContains: "out of range",
		},
		{
			name:        "byte without name",
			text:        "BYTE",
			wantErr:     true,
			errContains: "wrong number of parameters",
		},
		{
			name:        "byte with invalid identifier",
			text:        "BYTE 123invalid",
			wantErr:     true,
			errContains: "invalid identifier",
		},
		{
			name:        "byte with wrong operator",
			text:        "BYTE x + 10",
			wantErr:     true,
			errContains: "expected '=' or '@'",
		},
		{
			name:        "const without equals",
			text:        "BYTE CONST x 10",
			wantErr:     true,
			errContains: "expected '='",
		},
		{
			name:        "wrong keyword instead of CONST",
			text:        "BYTE VAR x = 10",
			wantErr:     true,
			errContains: "expected CONST keyword",
		},
		{
			name:        "duplicate declaration",
			text:        "BYTE 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.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			}

			cmd := &ByteCommand{}
			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 TestByteCommand_Generate(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	cmd := &ByteCommand{}
	line := preproc.Line{
		Text:           "BYTE x = 10",
		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 TestByteCommand_InFunctionScope(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

	// Simulate being inside a function
	// We'd need to push function context, but for now we can test the scoping manually
	scope := "myFunc"
	ctx.SymbolTable.AddVar("localVar", scope, compiler.KindByte, 5, preproc.Line{Filename: "test.c65", LineNo: 1})

	// Check it was added with correct scope
	sym := ctx.SymbolTable.Lookup("localVar", []string{scope})
	if sym == nil {
		t.Fatal("Expected local variable to be declared")
	}
	if sym.Scope != scope {
		t.Errorf("Expected scope %q, got %q", scope, sym.Scope)
	}
	if sym.FullName() != "myFunc_localVar" {
		t.Errorf("Expected full name myFunc_localVar, got %q", sym.FullName())
	}
}

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

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

	// Now declare a byte using the constant in an expression
	cmd := &ByteCommand{}
	line := preproc.Line{
		Text:           "BYTE 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 != 210 {
		t.Errorf("Expected value 210 (200+10), got %d", sym.Value)
	}
}

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

	cmd := &ByteCommand{}
	line := preproc.Line{
		Text:           "BYTE x = UNKNOWN",
		Filename:       "test.c65",
		LineNo:         1,
		Kind:           preproc.Source,
		PragmaSetIndex: 0,
	}

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

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

	cmd := &ByteCommand{}
	line := preproc.Line{
		Text:           "BYTE 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 REGISTER in global scope")
	}
	if !strings.Contains(err.Error(), "REGISTER") {
		t.Errorf("Error should mention REGISTER, got: %v", err)
	}
}

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

	cmd := &ByteCommand{}
	line := preproc.Line{
		Text:           "BYTE REGISTER x @ $02",
		Filename:       "test.c65",
		LineNo:         1,
		Kind:           preproc.Source,
		PragmaSetIndex: 0,
	}

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

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

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

	err := cmd.Interpret(line, ctx)
	if err == nil {
		t.Fatal("Expected error for REGISTER CONST")
	}
}
