package commands

import (
	"strings"
	"testing"

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

func TestWhileBasicEqual(t *testing.T) {
	tests := []struct {
		name      string
		whileLine string
		setupVars func(*compiler.SymbolTable)
		wantWhile []string
		wantWend  []string
	}{
		{
			name:      "byte var == byte literal",
			whileLine: "WHILE x = 10",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantWhile: []string{
				"_LOOPSTART1",
				"\tlda x",
				"\tcmp #$0a",
				"\tbne _LOOPEND1",
			},
			wantWend: []string{
				"\tjmp _LOOPSTART1",
				"_LOOPEND1",
			},
		},
		{
			name:      "word var == word literal",
			whileLine: "WHILE x = 1000",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantWhile: []string{
				"_LOOPSTART1",
				"\tlda x",
				"\tcmp #$e8",
				"\tbne _LOOPEND1",
				"\tlda x+1",
				"\tcmp #$03",
				"\tbne _LOOPEND1",
			},
			wantWend: []string{
				"\tjmp _LOOPSTART1",
				"_LOOPEND1",
			},
		},
	}

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

			whileCmd := &WhileCommand{}
			wendCmd := &WendCommand{}

			whileLine := preproc.Line{
				Text:           tt.whileLine,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}
			wendLine := preproc.Line{
				Text:           "WEND",
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

			if err := whileCmd.Interpret(whileLine, ctx); err != nil {
				t.Fatalf("WHILE Interpret() error = %v", err)
			}

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

			if err := wendCmd.Interpret(wendLine, ctx); err != nil {
				t.Fatalf("WEND Interpret() error = %v", err)
			}

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

			if !equalAsm(whileAsm, tt.wantWhile) {
				t.Errorf("WHILE Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(whileAsm, "\n"),
					strings.Join(tt.wantWhile, "\n"))
			}
			if !equalAsm(wendAsm, tt.wantWend) {
				t.Errorf("WEND Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(wendAsm, "\n"),
					strings.Join(tt.wantWend, "\n"))
			}
		})
	}
}

func TestWhileAllOperators(t *testing.T) {
	tests := []struct {
		name     string
		line     string
		wantInst string
	}{
		{"equal", "WHILE x = 10", "bne"},
		{"not equal", "WHILE x <> 10", "beq"},
		{"greater", "WHILE x > 10", "bcs"},
		{"less", "WHILE x < 10", "bcs"},
		{"greater equal", "WHILE x >= 10", "bcc"},
		{"less equal", "WHILE x <= 10", "bcc"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pragma := preproc.NewPragma()
			ctx := compiler.NewCompilerContext(pragma)
			ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

			cmd := &WhileCommand{}
			line := preproc.Line{
				Text:           tt.line,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

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

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

			found := false
			for _, inst := range asm {
				if strings.Contains(inst, tt.wantInst) {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("Expected %s instruction not found in: %v", tt.wantInst, asm)
			}
		})
	}
}

func TestWhileMixedTypes(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)
	ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	cmd := &WhileCommand{}
	line := preproc.Line{
		Text:           "WHILE x < y",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

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

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

	foundHighByteCheck := false
	for _, inst := range asm {
		if strings.Contains(inst, "y+1") {
			foundHighByteCheck = true
			break
		}
	}
	if !foundHighByteCheck {
		t.Error("Expected high byte check for word in mixed comparison")
	}
}

func TestWhileBreak(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)
	ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	whileCmd := &WhileCommand{}
	breakCmd := &BreakCommand{}
	wendCmd := &WendCommand{}

	pragmaIdx := pragma.GetCurrentPragmaSetIndex()

	whileLine := preproc.Line{Text: "WHILE x < 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
	breakLine := preproc.Line{Text: "BREAK", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
	wendLine := preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}

	if err := whileCmd.Interpret(whileLine, ctx); err != nil {
		t.Fatalf("WHILE Interpret() error = %v", err)
	}

	whileAsm, _ := whileCmd.Generate(ctx)
	_ = whileAsm // body would go here

	if err := breakCmd.Interpret(breakLine, ctx); err != nil {
		t.Fatalf("BREAK Interpret() error = %v", err)
	}

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

	if err := wendCmd.Interpret(wendLine, ctx); err != nil {
		t.Fatalf("WEND Interpret() error = %v", err)
	}

	if len(breakAsm) != 1 || !strings.Contains(breakAsm[0], "jmp _LOOPEND") {
		t.Errorf("BREAK should jump to WEND label, got: %v", breakAsm)
	}
}

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

	cmd := &BreakCommand{}
	line := preproc.Line{
		Text:           "BREAK",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

	err := cmd.Interpret(line, ctx)
	if err == nil {
		t.Fatal("BREAK outside loop should fail")
	}
	if !strings.Contains(err.Error(), "not inside WHILE") {
		t.Errorf("Wrong error message: %v", err)
	}
}

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

	cmd := &WendCommand{}
	line := preproc.Line{
		Text:           "WEND",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

	err := cmd.Interpret(line, ctx)
	if err == nil {
		t.Fatal("WEND without WHILE should fail")
	}
	if !strings.Contains(err.Error(), "not inside WHILE") {
		t.Errorf("Wrong error message: %v", err)
	}
}

func TestWhileNested(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)
	ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
	ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	pragmaIdx := pragma.GetCurrentPragmaSetIndex()

	while1 := &WhileCommand{}
	while2 := &WhileCommand{}
	wend1 := &WendCommand{}
	wend2 := &WendCommand{}

	if err := while1.Interpret(preproc.Line{Text: "WHILE i < 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
		t.Fatalf("WHILE 1 error = %v", err)
	}
	asm1, err := while1.Generate(ctx)
	if err != nil {
		t.Fatalf("WHILE 1 Generate error = %v", err)
	}

	if err := while2.Interpret(preproc.Line{Text: "WHILE j < 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
		t.Fatalf("WHILE 2 error = %v", err)
	}
	asm2, err := while2.Generate(ctx)
	if err != nil {
		t.Fatalf("WHILE 2 Generate error = %v", err)
	}

	if err := wend2.Interpret(preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
		t.Fatalf("WEND 2 error = %v", err)
	}
	if err := wend1.Interpret(preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
		t.Fatalf("WEND 1 error = %v", err)
	}

	if asm1[0] == asm2[0] {
		t.Error("Nested loops should have different labels")
	}
}

func TestWhileLongJump(t *testing.T) {
	pragma := preproc.NewPragma()
	pragma.AddPragma("_P_USE_LONG_JUMP", "1")
	ctx := compiler.NewCompilerContext(pragma)
	ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	cmd := &WhileCommand{}
	line := preproc.Line{
		Text:           "WHILE x = 10",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

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

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

	foundJmp := false
	for _, inst := range asm {
		if strings.Contains(inst, "jmp") {
			foundJmp = true
			break
		}
	}
	if !foundJmp {
		t.Error("Long jump mode should contain JMP instruction")
	}
}

func TestWhileConstant(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)
	ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100, preproc.Line{Filename: "test.c65", LineNo: 1})
	ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	cmd := &WhileCommand{}
	line := preproc.Line{
		Text:           "WHILE x < MAX",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}

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

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

	found := false
	for _, inst := range asm {
		if strings.Contains(inst, "#$64") {
			found = true
			break
		}
	}
	if !found {
		t.Error("Constant should be folded to immediate value")
	}
}

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

	tests := []string{
		"WHILE x",
		"WHILE x =",
		"WHILE x = 10 extra",
	}

	for _, text := range tests {
		cmd := &WhileCommand{}
		line := preproc.Line{
			Text:           text,
			Kind:           preproc.Source,
			PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
		}

		err := cmd.Interpret(line, ctx)
		if err == nil {
			t.Errorf("Should fail with wrong param count: %s", text)
		}
	}
}

// Helper to compare assembly output
func equalAsm(got, want []string) bool {
	if len(got) != len(want) {
		return false
	}
	for i := range got {
		if got[i] != want[i] {
			return false
		}
	}
	return true
}
