package commands

import (
	"strings"
	"testing"

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

func TestLetCommand_WillHandle(t *testing.T) {
	cmd := &LetCommand{}

	tests := []struct {
		name string
		line string
		want bool
	}{
		{"old syntax LET/GET", "LET a GET b", true},
		{"old syntax LET/equals", "LET a = 10", true},
		{"new syntax", "result = value", true},
		{"not LET - arithmetic", "result = a + b", false}, // 5 params
		{"not LET - keyword", "ADD a TO b GIVING c", false},
		{"wrong param count", "LET a b", false},
		{"empty", "", false},
	}

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

func TestLetCommand_OldSyntax(t *testing.T) {
	tests := []struct {
		name      string
		line      string
		setupVars func(*compiler.SymbolTable)
		wantAsm   []string
		wantErr   bool
	}{
		{
			name: "LET byte GET byte",
			line: "LET a GET b",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("b", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda b",
				"\tsta a",
			},
		},
		{
			name: "LET byte = literal",
			line: "LET a = 100",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$64",
				"\tsta a",
			},
		},
		{
			name: "LET word GET word",
			line: "LET x GET y",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("y", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda y",
				"\tsta x",
				"\tlda y+1",
				"\tsta x+1",
			},
		},
		{
			name: "LET word = literal",
			line: "LET x = $1234",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$34",
				"\tsta x",
				"\tlda #$12",
				"\tsta x+1",
			},
		},
		{
			name: "LET word GET byte (zero-extend)",
			line: "LET x GET b",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("b", "", compiler.KindByte, 100, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda b",
				"\tsta x",
				"\tlda #0",
				"\tsta x+1",
			},
		},
		{
			name: "LET byte GET word (take low byte)",
			line: "LET b GET x",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("b", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("x", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda x",
				"\tsta b",
			},
		},
		{
			name: "LET word = $0000 (optimization)",
			line: "LET x = 0",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$00",
				"\tsta x",
				"\tsta x+1",
			},
		},
		{
			name: "LET word = $FFFF (optimization)",
			line: "LET x = $FFFF",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$ff",
				"\tsta x",
				"\tsta x+1",
			},
		},
		{
			name: "LET with constant",
			line: "LET a = MAXVAL",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddConst("MAXVAL", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$ff",
				"\tsta a",
			},
		},
		{
			name: "error: unknown destination",
			line: "LET unknown GET a",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantErr: true,
		},
		{
			name: "error: wrong separator",
			line: "LET a TO b",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("b", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantErr: true,
		},
		{
			name: "error: cannot assign to constant",
			line: "LET MAXVAL = 100",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddConst("MAXVAL", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := compiler.NewCompilerContext(&preproc.Pragma{})
			tt.setupVars(ctx.SymbolTable)

			cmd := &LetCommand{}
			line := preproc.Line{Text: tt.line, Kind: preproc.Source}

			err := cmd.Interpret(line, ctx)
			if tt.wantErr {
				if err == nil {
					t.Error("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("Interpret() error = %v", err)
			}

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

			if !equalAsmLet(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

func TestLetCommand_NewSyntax(t *testing.T) {
	tests := []struct {
		name      string
		line      string
		setupVars func(*compiler.SymbolTable)
		wantAsm   []string
		wantErr   bool
	}{
		{
			name: "byte = byte",
			line: "a = b",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("b", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda b",
				"\tsta a",
			},
		},
		{
			name: "byte = literal",
			line: "a = 100",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$64",
				"\tsta a",
			},
		},
		{
			name: "word = word",
			line: "x = y",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("y", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda y",
				"\tsta x",
				"\tlda y+1",
				"\tsta x+1",
			},
		},
		{
			name: "word = literal",
			line: "x = $1234",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$34",
				"\tsta x",
				"\tlda #$12",
				"\tsta x+1",
			},
		},
		{
			name: "word = byte (zero-extend)",
			line: "x = b",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("b", "", compiler.KindByte, 100, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda b",
				"\tsta x",
				"\tlda #0",
				"\tsta x+1",
			},
		},
		{
			name: "byte = word (take low byte)",
			line: "b = x",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("b", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddVar("x", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda x",
				"\tsta b",
			},
		},
		{
			name: "word = 0 (optimization)",
			line: "x = 0",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$00",
				"\tsta x",
				"\tsta x+1",
			},
		},
		{
			name: "word = $FFFF (optimization)",
			line: "x = 65535",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$ff",
				"\tsta x",
				"\tsta x+1",
			},
		},
		{
			name: "word = $0102 (different bytes, no optimization)",
			line: "x = $0102",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$02",
				"\tsta x",
				"\tlda #$01",
				"\tsta x+1",
			},
		},
		{
			name: "using constant",
			line: "a = MAXVAL",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
				st.AddConst("MAXVAL", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$ff",
				"\tsta a",
			},
		},
		{
			name: "expression with constant",
			line: "a = 10+5",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantAsm: []string{
				"\tlda #$0f",
				"\tsta a",
			},
		},
		{
			name: "error: unknown destination",
			line: "unknown = a",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("a", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantErr: true,
		},
		{
			name: "error: cannot assign to constant",
			line: "MAXVAL = 100",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddConst("MAXVAL", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := compiler.NewCompilerContext(&preproc.Pragma{})
			tt.setupVars(ctx.SymbolTable)

			cmd := &LetCommand{}
			line := preproc.Line{Text: tt.line, Kind: preproc.Source}

			err := cmd.Interpret(line, ctx)
			if tt.wantErr {
				if err == nil {
					t.Error("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("Interpret() error = %v", err)
			}

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

			if !equalAsmLet(asm, tt.wantAsm) {
				t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(asm, "\n"),
					strings.Join(tt.wantAsm, "\n"))
			}
		})
	}
}

// equalAsmLet compares two assembly slices for equality
func equalAsmLet(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}
