package controllers

import (
    "bytes"
    "fmt"
    "image"
    "image/png"
    _ "image/gif"
    _ "image/jpeg"
    "mime/multipart"
    "io"
    "net/http"
    "os"
    "path/filepath"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
)

// sanitizeAndWriteLogo trims white/transparent borders and resizes to fixed height (64px), then writes PNG to outPath.
func sanitizeAndWriteLogo(data []byte, outPath string) error {
    img, _, err := image.Decode(bytes.NewReader(data))
    if err != nil {
        return err
    }
    b := img.Bounds()
    minX, minY := b.Max.X, b.Max.Y
    maxX, maxY := b.Min.X, b.Min.Y
    for y := b.Min.Y; y < b.Max.Y; y++ {
        for x := b.Min.X; x < b.Max.X; x++ {
            r, g, bl, a := img.At(x, y).RGBA()
            if a <= 0x10 { // near transparent
                continue
            }
            rr, gg, bb := uint8(r>>8), uint8(g>>8), uint8(bl>>8)
            if rr > 245 && gg > 245 && bb > 245 { // nearly white background
                continue
            }
            if x < minX { minX = x }
            if y < minY { minY = y }
            if x > maxX { maxX = x }
            if y > maxY { maxY = y }
        }
    }
    if minX >= maxX || minY >= maxY {
        // fallback to full image
        minX, minY = b.Min.X, b.Min.Y
        maxX, maxY = b.Max.X-1, b.Max.Y-1
    }
    cw, ch := maxX-minX+1, maxY-minY+1
    nrgba := image.NewNRGBA(image.Rect(0, 0, cw, ch))
    for y := 0; y < ch; y++ {
        for x := 0; x < cw; x++ {
            nrgba.Set(x, y, img.At(minX+x, minY+y))
        }
    }
    // resize to 64px height using nearest-neighbor
    targetH := 64
    if ch != targetH {
        targetW := int(float64(cw) * float64(targetH) / float64(ch))
        if targetW < 1 { targetW = 1 }
        resized := image.NewNRGBA(image.Rect(0, 0, targetW, targetH))
        for y2 := 0; y2 < targetH; y2++ {
            srcY := y2 * ch / targetH
            for x2 := 0; x2 < targetW; x2++ {
                srcX := x2 * cw / targetW
                c := nrgba.NRGBAAt(srcX, srcY)
                resized.SetNRGBA(x2, y2, c)
            }
        }
        nrgba = resized
    }
    // write PNG
    if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil { return err }
    f, err := os.Create(outPath)
    if err != nil {
        return err
    }
    defer f.Close()
    return png.Encode(f, nrgba)
}

// ensureUniqueFilename ensures name does not collide within dir, adding -1, -2 etc.
func ensureUniqueFilename(dir, name string) string {
    base := name
    ext := ""
    if i := strings.LastIndex(name, "."); i >= 0 {
        base = name[:i]
        ext = name[i:]
    }
    try := name
    idx := 1
    for {
        if _, err := os.Stat(filepath.Join(dir, try)); os.IsNotExist(err) {
            return try
        }
        try = fmt.Sprintf("%s-%d%s", base, idx, ext)
        idx++
    }
}

// ListSponsors returns list of sponsor logo URLs under /uploads/sponsors
func (c *ScoreboardController) ListSponsors(ctx *gin.Context) {
    entries, err := os.ReadDir(filepath.Join("uploads", "sponsors"))
    if err != nil {
        ctx.JSON(http.StatusOK, []string{})
        return
    }
    out := make([]string, 0, len(entries))
    for _, e := range entries {
        if e.IsDir() { continue }
        name := e.Name()
        lower := strings.ToLower(name)
        if strings.HasSuffix(lower, ".png") || strings.HasSuffix(lower, ".jpg") || strings.HasSuffix(lower, ".jpeg") || strings.HasSuffix(lower, ".gif") || strings.HasSuffix(lower, ".webp") || strings.HasSuffix(lower, ".svg") {
            out = append(out, "/uploads/sponsors/"+name)
        }
    }
    ctx.JSON(http.StatusOK, out)
}

// UploadSponsors accepts multipart form files under field name "files" (or single "file")
func (c *ScoreboardController) UploadSponsors(ctx *gin.Context) {
    if err := ctx.Request.ParseMultipartForm(200 << 20); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid upload"})
        return
    }
    _ = os.MkdirAll(filepath.Join("uploads", "sponsors"), 0o755)

    saved := 0
    if ctx.Request.MultipartForm != nil {
        files := ctx.Request.MultipartForm.File["files"]
        if len(files) == 0 {
            if f, hdr, err := ctx.Request.FormFile("file"); err == nil {
                _ = f.Close()
                files = []*multipart.FileHeader{hdr}
            }
        }
        for _, hdr := range files {
            if hdr == nil { continue }
            src, err := hdr.Open()
            if err != nil { continue }
            // do not defer: loop
            name := sanitizeFilename(hdr.Filename)
            if name == "" { name = fmt.Sprintf("sponsor-%d", time.Now().UnixNano()) }
            base := name
            if i := strings.LastIndex(name, "."); i >= 0 { base = name[:i] }
            outName := ensureUniqueFilename(filepath.Join("uploads", "sponsors"), base+".png")
            outPath := filepath.Join("uploads", "sponsors", outName)

            var buf bytes.Buffer
            if _, err := io.Copy(&buf, src); err == nil {
                if err := sanitizeAndWriteLogo(buf.Bytes(), outPath); err == nil {
                    saved++
                } else {
                    // Fallback: write original bytes with original extension
                    rawName := ensureUniqueFilename(filepath.Join("uploads", "sponsors"), name)
                    rawPath := filepath.Join("uploads", "sponsors", rawName)
                    _ = os.WriteFile(rawPath, buf.Bytes(), 0o644)
                    saved++
                }
            }
            _ = src.Close()
        }
    }
    ctx.JSON(http.StatusOK, gin.H{"saved": saved})
}

// DeleteSponsor deletes a sponsor logo by filename (?name=)
func (c *ScoreboardController) DeleteSponsor(ctx *gin.Context) {
    name := sanitizeFilename(ctx.Query("name"))
    if name == "" {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": "missing name"})
        return
    }
    p := filepath.Join("uploads", "sponsors", name)
    if _, err := os.Stat(p); os.IsNotExist(err) {
        ctx.JSON(http.StatusNotFound, gin.H{"error": "not found"})
        return
    }
    if err := os.Remove(p); err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{"error": "cannot delete"})
        return
    }
    ctx.JSON(http.StatusOK, gin.H{"ok": true})
}

// GetQR returns the current QR image URL if present
func (c *ScoreboardController) GetQR(ctx *gin.Context) {
    path := filepath.Join("uploads", "qr.png")
    if _, err := os.Stat(path); err == nil {
        ctx.JSON(http.StatusOK, gin.H{"qr": "/uploads/qr.png"})
        return
    }
    ctx.JSON(http.StatusOK, gin.H{"qr": ""})
}

// UploadQR accepts a single file and stores/overwrites uploads/qr.png
func (c *ScoreboardController) UploadQR(ctx *gin.Context) {
    file, _, err := ctx.Request.FormFile("file")
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": "file not provided (field 'file')"})
        return
    }
    defer file.Close()
    _ = os.MkdirAll("uploads", 0o755)
    out, err := os.Create(filepath.Join("uploads", "qr.png"))
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{"error": "cannot save"})
        return
    }
    defer out.Close()
    if _, err := io.Copy(out, file); err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{"error": "write failed"})
        return
    }
    ctx.JSON(http.StatusOK, gin.H{"ok": true})
}
