package commands

import (
	"strings"
	"testing"

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

func TestIfBasicEqual(t *testing.T) {
	tests := []struct {
		name      string
		ifLine    string
		setupVars func(*compiler.SymbolTable)
		wantIf    []string
		wantEndif []string
	}{
		{
			name:   "byte var == byte literal",
			ifLine: "IF x = 10",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantIf: []string{
				"\tlda x",
				"\tcmp #$0a",
				"\tbne _I1",
			},
			wantEndif: []string{
				"_I1",
			},
		},
		{
			name:   "word var == word literal",
			ifLine: "IF x = 1000",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantIf: []string{
				"\tlda x",
				"\tcmp #$e8",
				"\tbne _I1",
				"\tlda x+1",
				"\tcmp #$03",
				"\tbne _I1",
			},
			wantEndif: []string{
				"_I1",
			},
		},
	}

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

			ifCmd := &IfCommand{}
			endifCmd := &EndIfCommand{}

			ifLine := preproc.Line{
				Text:           tt.ifLine,
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}
			endifLine := preproc.Line{
				Text:           "ENDIF",
				Kind:           preproc.Source,
				PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
			}

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

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

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

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

			if !equalAsm(ifAsm, tt.wantIf) {
				t.Errorf("IF Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(ifAsm, "\n"),
					strings.Join(tt.wantIf, "\n"))
			}
			if !equalAsm(endifAsm, tt.wantEndif) {
				t.Errorf("ENDIF Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(endifAsm, "\n"),
					strings.Join(tt.wantEndif, "\n"))
			}
		})
	}
}

func TestIfElseEndif(t *testing.T) {
	tests := []struct {
		name      string
		ifLine    string
		setupVars func(*compiler.SymbolTable)
		wantIf    []string
		wantElse  []string
		wantEndif []string
	}{
		{
			name:   "byte var with else",
			ifLine: "IF x = 10",
			setupVars: func(st *compiler.SymbolTable) {
				st.AddVar("x", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
			},
			wantIf: []string{
				"\tlda x",
				"\tcmp #$0a",
				"\tbne _I1",
			},
			wantElse: []string{
				"\tjmp _I2",
				"_I1",
			},
			wantEndif: []string{
				"_I2",
			},
		},
	}

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

			ifCmd := &IfCommand{}
			elseCmd := &ElseCommand{}
			endifCmd := &EndIfCommand{}

			pragmaIdx := pragma.GetCurrentPragmaSetIndex()

			if err := ifCmd.Interpret(preproc.Line{Text: tt.ifLine, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
				t.Fatalf("IF Interpret() error = %v", err)
			}

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

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

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

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

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

			if !equalAsm(ifAsm, tt.wantIf) {
				t.Errorf("IF Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(ifAsm, "\n"),
					strings.Join(tt.wantIf, "\n"))
			}
			if !equalAsm(elseAsm, tt.wantElse) {
				t.Errorf("ELSE Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(elseAsm, "\n"),
					strings.Join(tt.wantElse, "\n"))
			}
			if !equalAsm(endifAsm, tt.wantEndif) {
				t.Errorf("ENDIF Generate() mismatch\ngot:\n%s\nwant:\n%s",
					strings.Join(endifAsm, "\n"),
					strings.Join(tt.wantEndif, "\n"))
			}
		})
	}
}

func TestIfAllOperators(t *testing.T) {
	tests := []struct {
		name     string
		line     string
		wantInst string
	}{
		{"equal", "IF x = 10", "bne"},
		{"not equal", "IF x <> 10", "beq"},
		{"greater", "IF x > 10", "bcs"},
		{"less", "IF x < 10", "bcs"},
		{"greater equal", "IF x >= 10", "bcc"},
		{"less equal", "IF 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 := &IfCommand{}
			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 TestIfMixedTypes(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 := &IfCommand{}
	line := preproc.Line{
		Text:           "IF 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 TestEndifWithoutIf(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

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

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

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

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

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

func TestIfNested(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.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	pragmaIdx := pragma.GetCurrentPragmaSetIndex()

	if1 := &IfCommand{}
	if2 := &IfCommand{}
	endif1 := &EndIfCommand{}
	endif2 := &EndIfCommand{}

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

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

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

	// Find labels in asm output
	label1 := findLabel(asm1)
	label2 := findLabel(asm2)

	if label1 == label2 {
		t.Error("Nested IFs should have different labels")
	}
	if label1 == "" || label2 == "" {
		t.Error("Should generate labels for both IFs")
	}
}

func TestIfNestedWithElse(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.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})

	pragmaIdx := pragma.GetCurrentPragmaSetIndex()

	if1 := &IfCommand{}
	else1 := &ElseCommand{}
	if2 := &IfCommand{}
	endif2 := &EndIfCommand{}
	endif1 := &EndIfCommand{}

	if err := if1.Interpret(preproc.Line{Text: "IF x = 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
		t.Fatalf("IF 1 error = %v", err)
	}
	if1.Generate(ctx)

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

	if err := if2.Interpret(preproc.Line{Text: "IF y = 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
		t.Fatalf("IF 2 error = %v", err)
	}
	if2.Generate(ctx)

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

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

	// If this doesn't crash, nesting with ELSE works
}

func TestIfLongJump(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 := &IfCommand{}
	line := preproc.Line{
		Text:           "IF 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 TestIfConstant(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 := &IfCommand{}
	line := preproc.Line{
		Text:           "IF 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 TestIfWrongParamCount(t *testing.T) {
	pragma := preproc.NewPragma()
	ctx := compiler.NewCompilerContext(pragma)

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

	for _, text := range tests {
		cmd := &IfCommand{}
		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)
		}
	}
}

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

	// Setup IF first
	ifCmd := &IfCommand{}
	ifCmd.Interpret(preproc.Line{
		Text:           "IF x = 10",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}, ctx)

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

	err := cmd.Interpret(line, ctx)
	if err == nil {
		t.Error("ELSE with extra params should fail")
	}
}

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

	// Setup IF first
	ifCmd := &IfCommand{}
	ifCmd.Interpret(preproc.Line{
		Text:           "IF x = 10",
		Kind:           preproc.Source,
		PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
	}, ctx)

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

	err := cmd.Interpret(line, ctx)
	if err == nil {
		t.Error("ENDIF with extra params should fail")
	}
}

func TestIfConstantFolding(t *testing.T) {
	tests := []struct {
		name       string
		ifLine     string
		shouldSkip bool
	}{
		{"true condition", "IF 10 = 10", false},
		{"false condition", "IF 10 = 5", true},
		{"true not equal", "IF 10 <> 5", false},
		{"false not equal", "IF 10 <> 10", true},
	}

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

			cmd := &IfCommand{}
			line := preproc.Line{
				Text:           tt.ifLine,
				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)
			}

			hasJump := false
			for _, inst := range asm {
				if strings.Contains(inst, "jmp") {
					hasJump = true
					break
				}
			}

			if tt.shouldSkip && !hasJump {
				t.Error("False constant should generate JMP to skip block")
			}
			if !tt.shouldSkip && len(asm) > 0 && hasJump {
				t.Error("True constant should not generate JMP")
			}
		})
	}
}

// Helper to find label in assembly
func findLabel(asm []string) string {
	for _, line := range asm {
		if strings.Contains(line, "_I") && !strings.HasPrefix(strings.TrimSpace(line), "\t") {
			return strings.TrimSpace(line)
		}
		if strings.Contains(line, "bne") || strings.Contains(line, "beq") {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				return parts[1]
			}
		}
	}
	return ""
}

/*
// 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
}
*/
