package renderer

import (
	"testing"

	"gitea.dockr.co/kev/go-jdenticon/internal/engine"
)

func TestNewBaseRenderer(t *testing.T) {
	iconSize := 100
	r := NewBaseRenderer(iconSize)

	if r.GetSize() != iconSize {
		t.Errorf("Expected icon size %d, got %d", iconSize, r.GetSize())
	}

	if len(r.GetCurrentPath()) != 0 {
		t.Errorf("Expected empty path, got %d commands", len(r.GetCurrentPath()))
	}

	if r.GetCurrentColor() != "" {
		t.Errorf("Expected empty current color, got %s", r.GetCurrentColor())
	}

	bg, bgOp := r.GetBackground()
	if bg != "" || bgOp != 0 {
		t.Errorf("Expected empty background, got %s with opacity %f", bg, bgOp)
	}
}

func TestBaseRendererSetBackground(t *testing.T) {
	r := NewBaseRenderer(100)

	color := "#ff0000"
	opacity := 0.5

	r.SetBackground(color, opacity)

	bg, bgOp := r.GetBackground()
	if bg != color {
		t.Errorf("Expected background color %s, got %s", color, bg)
	}
	if bgOp != opacity {
		t.Errorf("Expected background opacity %f, got %f", opacity, bgOp)
	}
}

func TestBaseRendererBeginShape(t *testing.T) {
	r := NewBaseRenderer(100)

	color := "#00ff00"
	r.BeginShape(color)

	if r.GetCurrentColor() != color {
		t.Errorf("Expected current color %s, got %s", color, r.GetCurrentColor())
	}

	// Path should be reset when beginning a shape
	if len(r.GetCurrentPath()) != 0 {
		t.Errorf("Expected empty path after BeginShape, got %d commands", len(r.GetCurrentPath()))
	}
}

func TestBaseRendererMoveTo(t *testing.T) {
	r := NewBaseRenderer(100)

	x, y := 10.5, 20.3
	r.MoveTo(x, y)

	path := r.GetCurrentPath()
	if len(path) != 1 {
		t.Fatalf("Expected 1 path command, got %d", len(path))
	}

	cmd := path[0]
	if cmd.Type != MoveToCommand {
		t.Errorf("Expected MoveToCommand, got %v", cmd.Type)
	}

	if len(cmd.Points) != 1 {
		t.Fatalf("Expected 1 point, got %d", len(cmd.Points))
	}

	point := cmd.Points[0]
	if point.X != x || point.Y != y {
		t.Errorf("Expected point (%f, %f), got (%f, %f)", x, y, point.X, point.Y)
	}
}

func TestBaseRendererLineTo(t *testing.T) {
	r := NewBaseRenderer(100)

	// Move to start point first
	r.MoveTo(0, 0)

	x, y := 15.7, 25.9
	r.LineTo(x, y)

	path := r.GetCurrentPath()
	if len(path) != 2 {
		t.Fatalf("Expected 2 path commands, got %d", len(path))
	}

	cmd := path[1] // Second command should be LineTo
	if cmd.Type != LineToCommand {
		t.Errorf("Expected LineToCommand, got %v", cmd.Type)
	}

	if len(cmd.Points) != 1 {
		t.Fatalf("Expected 1 point, got %d", len(cmd.Points))
	}

	point := cmd.Points[0]
	if point.X != x || point.Y != y {
		t.Errorf("Expected point (%f, %f), got (%f, %f)", x, y, point.X, point.Y)
	}
}

func TestBaseRendererCurveTo(t *testing.T) {
	r := NewBaseRenderer(100)

	// Move to start point first
	r.MoveTo(0, 0)

	x1, y1 := 10.0, 5.0
	x2, y2 := 20.0, 15.0
	x, y := 30.0, 25.0

	r.CurveTo(x1, y1, x2, y2, x, y)

	path := r.GetCurrentPath()
	if len(path) != 2 {
		t.Fatalf("Expected 2 path commands, got %d", len(path))
	}

	cmd := path[1] // Second command should be CurveTo
	if cmd.Type != CurveToCommand {
		t.Errorf("Expected CurveToCommand, got %v", cmd.Type)
	}

	if len(cmd.Points) != 3 {
		t.Fatalf("Expected 3 points, got %d", len(cmd.Points))
	}

	// Check control points and end point
	if cmd.Points[0].X != x1 || cmd.Points[0].Y != y1 {
		t.Errorf("Expected first control point (%f, %f), got (%f, %f)", x1, y1, cmd.Points[0].X, cmd.Points[0].Y)
	}
	if cmd.Points[1].X != x2 || cmd.Points[1].Y != y2 {
		t.Errorf("Expected second control point (%f, %f), got (%f, %f)", x2, y2, cmd.Points[1].X, cmd.Points[1].Y)
	}
	if cmd.Points[2].X != x || cmd.Points[2].Y != y {
		t.Errorf("Expected end point (%f, %f), got (%f, %f)", x, y, cmd.Points[2].X, cmd.Points[2].Y)
	}
}

func TestBaseRendererClosePath(t *testing.T) {
	r := NewBaseRenderer(100)

	// Move to start point first
	r.MoveTo(0, 0)
	r.LineTo(10, 10)
	r.ClosePath()

	path := r.GetCurrentPath()
	if len(path) != 3 {
		t.Fatalf("Expected 3 path commands, got %d", len(path))
	}

	cmd := path[2] // Third command should be ClosePath
	if cmd.Type != ClosePathCommand {
		t.Errorf("Expected ClosePathCommand, got %v", cmd.Type)
	}

	if len(cmd.Points) != 0 {
		t.Errorf("Expected 0 points for ClosePath, got %d", len(cmd.Points))
	}
}

func TestBaseRendererAddPolygon(t *testing.T) {
	r := NewBaseRenderer(100)
	r.BeginShape("#ff0000")

	points := []engine.Point{
		{X: 0, Y: 0},
		{X: 10, Y: 0},
		{X: 10, Y: 10},
		{X: 0, Y: 10},
	}

	r.AddPolygon(points)

	path := r.GetCurrentPath()

	// Should have MoveTo + 3 LineTo + ClosePath = 5 commands
	expectedCommands := len(points) + 1 // +1 for ClosePath
	if len(path) != expectedCommands {
		t.Fatalf("Expected %d path commands, got %d", expectedCommands, len(path))
	}

	// Check first command is MoveTo
	if path[0].Type != MoveToCommand {
		t.Errorf("Expected first command to be MoveTo, got %v", path[0].Type)
	}

	// Check last command is ClosePath
	if path[len(path)-1].Type != ClosePathCommand {
		t.Errorf("Expected last command to be ClosePath, got %v", path[len(path)-1].Type)
	}
}

func TestBaseRendererAddRectangle(t *testing.T) {
	r := NewBaseRenderer(100)
	r.BeginShape("#0000ff")

	x, y, width, height := 5.0, 10.0, 20.0, 15.0
	r.AddRectangle(x, y, width, height)

	path := r.GetCurrentPath()

	// Should have MoveTo + 3 LineTo + ClosePath = 5 commands
	if len(path) != 5 {
		t.Fatalf("Expected 5 path commands, got %d", len(path))
	}

	// Verify the rectangle points
	expectedPoints := []engine.Point{
		{X: x, Y: y},                  // bottom-left
		{X: x + width, Y: y},          // bottom-right
		{X: x + width, Y: y + height}, // top-right
		{X: x, Y: y + height},         // top-left
	}

	// Check MoveTo point
	if path[0].Points[0] != expectedPoints[0] {
		t.Errorf("Expected first point %v, got %v", expectedPoints[0], path[0].Points[0])
	}

	// Check LineTo points
	for i := 1; i < 4; i++ {
		if path[i].Type != LineToCommand {
			t.Errorf("Expected LineTo command at index %d, got %v", i, path[i].Type)
		}
		if path[i].Points[0] != expectedPoints[i] {
			t.Errorf("Expected point %v at index %d, got %v", expectedPoints[i], i, path[i].Points[0])
		}
	}
}

func TestBaseRendererAddTriangle(t *testing.T) {
	r := NewBaseRenderer(100)
	r.BeginShape("#00ffff")

	p1 := engine.Point{X: 0, Y: 0}
	p2 := engine.Point{X: 10, Y: 0}
	p3 := engine.Point{X: 5, Y: 10}

	r.AddTriangle(p1, p2, p3)

	path := r.GetCurrentPath()

	// Should have MoveTo + 2 LineTo + ClosePath = 4 commands
	if len(path) != 4 {
		t.Fatalf("Expected 4 path commands, got %d", len(path))
	}

	// Check the triangle points
	if path[0].Points[0] != p1 {
		t.Errorf("Expected first point %v, got %v", p1, path[0].Points[0])
	}
	if path[1].Points[0] != p2 {
		t.Errorf("Expected second point %v, got %v", p2, path[1].Points[0])
	}
	if path[2].Points[0] != p3 {
		t.Errorf("Expected third point %v, got %v", p3, path[2].Points[0])
	}
}

func TestBaseRendererAddCircle(t *testing.T) {
	r := NewBaseRenderer(100)
	r.BeginShape("#ffff00")

	center := engine.Point{X: 50, Y: 50}
	radius := 25.0

	r.AddCircle(center, radius, false)

	path := r.GetCurrentPath()

	// Should have MoveTo + 4 CurveTo + ClosePath = 6 commands
	if len(path) != 6 {
		t.Fatalf("Expected 6 path commands for circle, got %d", len(path))
	}

	// Check first command is MoveTo
	if path[0].Type != MoveToCommand {
		t.Errorf("Expected first command to be MoveTo, got %v", path[0].Type)
	}

	// Check that we have 4 CurveTo commands
	curveCount := 0
	for i := 1; i < len(path)-1; i++ {
		if path[i].Type == CurveToCommand {
			curveCount++
		}
	}
	if curveCount != 4 {
		t.Errorf("Expected 4 CurveTo commands for circle, got %d", curveCount)
	}

	// Check last command is ClosePath
	if path[len(path)-1].Type != ClosePathCommand {
		t.Errorf("Expected last command to be ClosePath, got %v", path[len(path)-1].Type)
	}
}

func TestBaseRendererClear(t *testing.T) {
	r := NewBaseRenderer(100)

	// Set some state
	r.BeginShape("#ff0000")
	r.SetBackground("#ffffff", 0.8)
	r.MoveTo(10, 20)
	r.LineTo(30, 40)

	// Verify state is set
	if r.GetCurrentColor() == "" {
		t.Error("Expected current color to be set before clear")
	}
	if len(r.GetCurrentPath()) == 0 {
		t.Error("Expected path commands before clear")
	}

	// Clear the renderer
	r.Clear()

	// Verify state is cleared
	if r.GetCurrentColor() != "" {
		t.Errorf("Expected empty current color after clear, got %s", r.GetCurrentColor())
	}
	if len(r.GetCurrentPath()) != 0 {
		t.Errorf("Expected empty path after clear, got %d commands", len(r.GetCurrentPath()))
	}

	bg, bgOp := r.GetBackground()
	if bg != "" || bgOp != 0 {
		t.Errorf("Expected empty background after clear, got %s with opacity %f", bg, bgOp)
	}
}

func TestBaseRendererEmptyPolygon(t *testing.T) {
	r := NewBaseRenderer(100)
	r.BeginShape("#ff0000")

	// Test with empty points slice
	r.AddPolygon([]engine.Point{})

	path := r.GetCurrentPath()
	if len(path) != 0 {
		t.Errorf("Expected no path commands for empty polygon, got %d", len(path))
	}
}
