package renderer

import (
	"bytes"
	"image/png"
	"testing"

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

func TestNewPNGRenderer(t *testing.T) {
	renderer := NewPNGRenderer(100)

	if renderer.GetSize() != 100 {
		t.Errorf("NewPNGRenderer(100).GetSize() = %v, want 100", renderer.GetSize())
	}

	if renderer == nil {
		t.Error("PNGRenderer should be initialized")
	}
}

func TestPNGRenderer_SetBackground(t *testing.T) {
	renderer := NewPNGRenderer(50)

	renderer.SetBackground("#ff0000", 1.0)

	// Check that background was set on base renderer
	bg, op := renderer.GetBackground()
	if bg != "#ff0000" {
		t.Errorf("background color = %v, want #ff0000", bg)
	}
	if op != 1.0 {
		t.Errorf("background opacity = %v, want 1.0", op)
	}
}

func TestPNGRenderer_SetBackgroundWithOpacity(t *testing.T) {
	renderer := NewPNGRenderer(50)

	renderer.SetBackground("#00ff00", 0.5)

	bg, op := renderer.GetBackground()
	if bg != "#00ff00" {
		t.Errorf("background color = %v, want #00ff00", bg)
	}
	if op != 0.5 {
		t.Errorf("background opacity = %v, want 0.5", op)
	}
}

func TestPNGRenderer_BeginEndShape(t *testing.T) {
	renderer := NewPNGRenderer(100)

	renderer.BeginShape("#0000ff")

	// Check that current color was set
	if renderer.GetCurrentColor() != "#0000ff" {
		t.Errorf("currentColor = %v, want #0000ff", renderer.GetCurrentColor())
	}

	renderer.EndShape()
	// EndShape is a no-op for PNG, just verify it doesn't panic
}

func TestPNGRenderer_AddPolygon(t *testing.T) {
	renderer := NewPNGRenderer(100)
	renderer.BeginShape("#ff0000")

	// Create a simple triangle
	points := []engine.Point{
		{X: 10, Y: 10},
		{X: 30, Y: 10},
		{X: 20, Y: 30},
	}

	// Should not panic
	renderer.AddPolygon(points)
}

func TestPNGRenderer_AddPolygonEmpty(t *testing.T) {
	renderer := NewPNGRenderer(100)
	renderer.BeginShape("#ff0000")

	// Empty polygon should not panic
	renderer.AddPolygon([]engine.Point{})

	// Polygon with < 3 points should not panic
	renderer.AddPolygon([]engine.Point{{X: 10, Y: 10}})
	renderer.AddPolygon([]engine.Point{{X: 10, Y: 10}, {X: 20, Y: 20}})
}

func TestPNGRenderer_AddCircle(t *testing.T) {
	renderer := NewPNGRenderer(100)
	renderer.BeginShape("#00ff00")

	// Circle with center at (50, 50) and radius 20 means topLeft at (30, 30) and size 40
	topLeft := engine.Point{X: 30, Y: 30}
	size := 40.0

	// Should not panic
	renderer.AddCircle(topLeft, size, false)
}

func TestPNGRenderer_AddCircleInvert(t *testing.T) {
	renderer := NewPNGRenderer(100)

	// First fill with background
	renderer.SetBackground("#ffffff", 1.0)
	renderer.BeginShape("#ff0000")

	// Add inverted circle (should not panic)
	topLeft := engine.Point{X: 30, Y: 30}
	size := 40.0

	renderer.AddCircle(topLeft, size, true)
}

func TestPNGRenderer_ToPNG(t *testing.T) {
	renderer := NewPNGRenderer(50)
	renderer.SetBackground("#ffffff", 1.0)

	renderer.BeginShape("#ff0000")
	points := []engine.Point{
		{X: 10, Y: 10},
		{X: 40, Y: 10},
		{X: 40, Y: 40},
		{X: 10, Y: 40},
	}
	renderer.AddPolygon(points)

	pngData, err := renderer.ToPNG()
	if err != nil {
		t.Fatalf("Failed to generate PNG: %v", err)
	}

	if len(pngData) == 0 {
		t.Error("ToPNG() should return non-empty data")
	}

	// Verify it's valid PNG data by decoding it
	reader := bytes.NewReader(pngData)
	decodedImg, err := png.Decode(reader)
	if err != nil {
		t.Errorf("ToPNG() returned invalid PNG data: %v", err)
	}

	// Check dimensions
	bounds := decodedImg.Bounds()
	if bounds.Max.X != 50 || bounds.Max.Y != 50 {
		t.Errorf("decoded image bounds = %v, want 50x50", bounds)
	}
}

func TestPNGRenderer_ToPNGWithSize(t *testing.T) {
	renderer := NewPNGRenderer(50)
	renderer.SetBackground("#ffffff", 1.0)

	renderer.BeginShape("#ff0000")
	points := []engine.Point{
		{X: 10, Y: 10},
		{X: 40, Y: 10},
		{X: 40, Y: 40},
		{X: 10, Y: 40},
	}
	renderer.AddPolygon(points)

	// Test generating at different size
	pngData, err := renderer.ToPNGWithSize(100)
	if err != nil {
		t.Fatalf("Failed to generate PNG with size: %v", err)
	}

	if len(pngData) == 0 {
		t.Error("ToPNGWithSize() should return non-empty data")
	}

	// Verify it's valid PNG data by decoding it
	reader := bytes.NewReader(pngData)
	decodedImg, err := png.Decode(reader)
	if err != nil {
		t.Errorf("ToPNGWithSize() returned invalid PNG data: %v", err)
	}

	// Check dimensions - should be 100x100 instead of 50x50
	bounds := decodedImg.Bounds()
	if bounds.Max.X != 100 || bounds.Max.Y != 100 {
		t.Errorf("decoded image bounds = %v, want 100x100", bounds)
	}
}

func TestPNGRenderer_ToPNGEmpty(t *testing.T) {
	renderer := NewPNGRenderer(10)

	pngData, err := renderer.ToPNG()
	if err != nil {
		t.Fatalf("Failed to generate PNG: %v", err)
	}

	if len(pngData) == 0 {
		t.Error("ToPNG() should return data even for empty image")
	}

	// Should be valid PNG
	reader := bytes.NewReader(pngData)
	decodedImg, err := png.Decode(reader)
	if err != nil {
		t.Errorf("ToPNG() returned invalid PNG data: %v", err)
	}

	// Check dimensions
	bounds := decodedImg.Bounds()
	if bounds.Max.X != 10 || bounds.Max.Y != 10 {
		t.Errorf("decoded image bounds = %v, want 10x10", bounds)
	}
}

func BenchmarkPNGRenderer_ToPNG(b *testing.B) {
	renderer := NewPNGRenderer(200)
	renderer.SetBackground("#ffffff", 1.0)

	// Add some shapes for a realistic benchmark
	renderer.BeginShape("#ff0000")
	renderer.AddPolygon([]engine.Point{
		{X: 20, Y: 20}, {X: 80, Y: 20}, {X: 80, Y: 80}, {X: 20, Y: 80},
	})

	renderer.BeginShape("#00ff00")
	renderer.AddCircle(engine.Point{X: 100, Y: 100}, 30, false)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		pngData, err := renderer.ToPNG()
		if err != nil {
			b.Fatalf("Failed to generate PNG: %v", err)
		}
		if len(pngData) == 0 {
			b.Fatal("ToPNG returned empty data")
		}
	}
}
