package public

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/reporting"
)

const (
	maxContentReportImageSizeB   = 5 * 1024 * 1024
	maxContentReportMultipartMem = 8 * 1024 * 1024
	maxContentReportBodySizeB    = 8 * 1024 * 1024
)

// ContentReportStore is the public report intake storage port.
type ContentReportStore interface {
	CreateContentReport(context.Context, reporting.CreateContentReportParams) (reporting.ContentReport, error)
}

// ContentReportHandler owns authenticated public-site content report intake.
type ContentReportHandler struct {
	store ContentReportStore
}

// Create handles POST /api/v1/public/content-reports.
// It accepts one logged-in user content report with an optional screenshot image.
func (handler *ContentReportHandler) Create(ctx *gin.Context) {
	if handler.store == nil {
		api.Error(ctx, http.StatusServiceUnavailable, "Content reporting is not configured")
		return
	}
	authenticated, ok := api.AuthenticatedUserFromContext(ctx)
	if !ok {
		api.Error(ctx, http.StatusUnauthorized, "Unauthorized")
		return
	}

	ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, maxContentReportBodySizeB)
	if err := ctx.Request.ParseMultipartForm(maxContentReportMultipartMem); err != nil {
		api.Error(ctx, multipartFormErrorStatus(err), "Invalid multipart form")
		return
	}
	if ctx.Request.MultipartForm != nil {
		defer func() { _ = ctx.Request.MultipartForm.RemoveAll() }()
	}

	image, ok := readOptionalReportImage(ctx)
	if !ok {
		return
	}

	_, err := handler.store.CreateContentReport(ctx.Request.Context(), reporting.CreateContentReportParams{
		TargetURL:      ctx.PostForm("targetUrl"),
		Reason:         ctx.PostForm("reason"),
		Description:    ctx.PostForm("description"),
		Contact:        ctx.PostForm("contact"),
		ReporterUserID: authenticated.User.ID,
		ReporterIP:     ctx.ClientIP(),
		UserAgent:      ctx.Request.UserAgent(),
		Image:          image,
	})
	if err != nil {
		if errors.Is(err, reporting.ErrRateLimited) {
			api.Error(ctx, http.StatusTooManyRequests, "Too many report submissions; please try again later")
			return
		}
		api.StoreError(ctx, err)
		return
	}

	ctx.JSON(http.StatusCreated, dto.PublicContentReportCreateResponse{OK: true})
}

func multipartFormErrorStatus(err error) int {
	var maxBytesError *http.MaxBytesError
	if errors.As(err, &maxBytesError) {
		return http.StatusRequestEntityTooLarge
	}
	return http.StatusBadRequest
}

func readOptionalReportImage(ctx *gin.Context) (*reporting.ContentReportImageUpload, bool) {
	header, err := ctx.FormFile("image")
	if err != nil {
		if errors.Is(err, http.ErrMissingFile) {
			return nil, true
		}
		api.Error(ctx, http.StatusBadRequest, "Invalid image")
		return nil, false
	}
	if strings.TrimSpace(header.Filename) == "" && header.Size == 0 {
		return nil, true
	}
	opened, err := header.Open()
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid image")
		return nil, false
	}
	defer opened.Close()

	blob, err := io.ReadAll(io.LimitReader(opened, maxContentReportImageSizeB+1))
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid image")
		return nil, false
	}
	if len(blob) > maxContentReportImageSizeB {
		api.Error(ctx, http.StatusRequestEntityTooLarge, "Image is too large")
		return nil, false
	}
	if len(blob) == 0 {
		api.Error(ctx, http.StatusBadRequest, "Image is empty")
		return nil, false
	}
	contentType, ok := detectReportImageContentType(blob)
	if !ok {
		api.Error(ctx, http.StatusBadRequest, "Image must be PNG, JPEG, or WebP")
		return nil, false
	}

	return &reporting.ContentReportImageUpload{
		Filename:    cleanReportImageFilename(header.Filename, contentType),
		ContentType: contentType,
		Blob:        blob,
	}, true
}

func detectReportImageContentType(blob []byte) (string, bool) {
	if len(blob) >= 8 &&
		blob[0] == 0x89 &&
		blob[1] == 'P' &&
		blob[2] == 'N' &&
		blob[3] == 'G' &&
		blob[4] == '\r' &&
		blob[5] == '\n' &&
		blob[6] == 0x1a &&
		blob[7] == '\n' {
		return "image/png", true
	}
	if len(blob) >= 3 && blob[0] == 0xff && blob[1] == 0xd8 && blob[2] == 0xff {
		return "image/jpeg", true
	}
	if len(blob) >= 12 &&
		string(blob[0:4]) == "RIFF" &&
		string(blob[8:12]) == "WEBP" {
		return "image/webp", true
	}
	return "", false
}

func cleanReportImageFilename(raw string, contentType string) string {
	name := strings.TrimSpace(filepath.Base(raw))
	if name == "." || name == "/" || name == "" {
		switch contentType {
		case "image/png":
			return "report.png"
		case "image/jpeg":
			return "report.jpg"
		case "image/webp":
			return "report.webp"
		default:
			return "report-image"
		}
	}
	return fmt.Sprintf("%.180s", name)
}
