package attachment

import (
	"bytes"
	"image"
	"image/color"
	"image/jpeg"
	"image/png"
	"testing"
)

func TestGenerateThumbnailResizesLongestEdge(t *testing.T) {
	// 1600x900 source → longest edge 1600, must cap at 480 → 480x270.
	src := image.NewRGBA(image.Rect(0, 0, 1600, 900))
	for x := 0; x < 1600; x++ {
		for y := 0; y < 900; y++ {
			src.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255})
		}
	}
	var raw bytes.Buffer
	if err := jpeg.Encode(&raw, src, &jpeg.Options{Quality: 90}); err != nil {
		t.Fatalf("encode source: %v", err)
	}

	out, err := GenerateThumbnail(raw.Bytes(), "image/jpeg")
	if err != nil {
		t.Fatalf("GenerateThumbnail: %v", err)
	}
	if out.ContentType != "image/jpeg" {
		t.Errorf("ContentType = %q, want image/jpeg", out.ContentType)
	}
	if out.Width != 480 || out.Height != 270 {
		t.Errorf("dimensions = %dx%d, want 480x270", out.Width, out.Height)
	}
	decoded, _, err := image.Decode(bytes.NewReader(out.Blob))
	if err != nil {
		t.Fatalf("decode thumbnail: %v", err)
	}
	if decoded.Bounds().Dx() != 480 || decoded.Bounds().Dy() != 270 {
		t.Errorf("decoded bounds = %v, want 480x270", decoded.Bounds())
	}
}

func TestGenerateThumbnailDoesNotUpscale(t *testing.T) {
	// 100x80 source is already under the cap → stays 100x80, re-encoded jpeg.
	src := image.NewRGBA(image.Rect(0, 0, 100, 80))
	src.Set(0, 0, color.RGBA{R: 0, G: 255, B: 0, A: 255})
	var raw bytes.Buffer
	if err := jpeg.Encode(&raw, src, &jpeg.Options{Quality: 90}); err != nil {
		t.Fatalf("encode source: %v", err)
	}

	out, err := GenerateThumbnail(raw.Bytes(), "image/jpeg")
	if err != nil {
		t.Fatalf("GenerateThumbnail: %v", err)
	}
	if out.Width != 100 || out.Height != 80 {
		t.Errorf("dimensions = %dx%d, want 100x80 (no upscale)", out.Width, out.Height)
	}
}

func TestGenerateThumbnailRejectsUnsupported(t *testing.T) {
	// Random non-image bytes + an unsupported content type → ErrThumbnailNotSupported.
	if _, err := GenerateThumbnail([]byte("<svg></svg>"), "image/svg+xml"); err != ErrThumbnailNotSupported {
		t.Errorf("err = %v, want ErrThumbnailNotSupported", err)
	}
	if _, err := GenerateThumbnail(nil, "image/png"); err != ErrThumbnailNotSupported {
		t.Errorf("empty source err = %v, want ErrThumbnailNotSupported", err)
	}
}

func TestGenerateThumbnailRejectsDecompressionBomb(t *testing.T) {
	// A JPEG with DecodeConfig reporting > 40M pixels must be rejected before
	// full decode to prevent memory/CPU DoS.
	src := image.NewRGBA(image.Rect(0, 0, 8000, 6000)) // 48M pixels > 40M cap
	for x := 0; x < 100; x++ {
		for y := 0; y < 100; y++ {
			src.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255})
		}
	}
	var raw bytes.Buffer
	if err := jpeg.Encode(&raw, src, &jpeg.Options{Quality: 10}); err != nil {
		t.Fatalf("encode source: %v", err)
	}
	if _, err := GenerateThumbnail(raw.Bytes(), "image/jpeg"); err != ErrThumbnailNotSupported {
		t.Errorf("err = %v, want ErrThumbnailNotSupported for > 40M pixel image", err)
	}
}

func TestGenerateThumbnailFillsTransparentBackground(t *testing.T) {
	// A fully transparent PNG must produce a JPEG with white background,
	// not a black one. JPEG has no alpha channel so the compositing layer matters.
	src := image.NewRGBA(image.Rect(0, 0, 4, 4))
	// Leave all pixels fully transparent (R=0,G=0,B=0,A=0).
	var raw bytes.Buffer
	if err := png.Encode(&raw, src); err != nil {
		t.Fatalf("encode source: %v", err)
	}
	out, err := GenerateThumbnail(raw.Bytes(), "image/png")
	if err != nil {
		t.Fatalf("GenerateThumbnail: %v", err)
	}
	decoded, err := jpeg.Decode(bytes.NewReader(out.Blob))
	if err != nil {
		t.Fatalf("decode thumbnail: %v", err)
	}
	// Sample center pixel — should be white (255,255,255), not black (0,0,0).
	r, g, b, _ := decoded.At(1, 1).RGBA()
	if r < 60000 || g < 60000 || b < 60000 {
		t.Errorf("transparent background pixel = (%d,%d,%d), want near-white (>=60000/65535)", r, g, b)
	}
}
