package website

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"image"
	"io"
	"net/http"
	"os"

	"git.handmade.network/hmn/hmn/src/db"
	"git.handmade.network/hmn/hmn/src/models"
	"git.handmade.network/hmn/hmn/src/oops"
)

type SaveImageFileResult struct {
	ImageFile       *models.ImageFile
	ValidationError string
	FatalError      error
}

/*
Reads an image file from form data and saves it to the filesystem and the database.
If the file doesn't exist, this does nothing and returns 0 for the image file id.

NOTE(ben): Someday we should replace this with the asset system.
*/
func SaveImageFile(c *RequestContext, dbConn db.ConnOrTx, fileFieldName string, maxSize int64, filepath string) SaveImageFileResult {
	img, header, err := c.Req.FormFile(fileFieldName)
	filename := ""
	width := 0
	height := 0
	if err != nil && !errors.Is(err, http.ErrMissingFile) {
		return SaveImageFileResult{
			FatalError: oops.New(err, "failed to read uploaded file"),
		}
	}

	if header != nil {
		if header.Size > maxSize {
			return SaveImageFileResult{
				ValidationError: fmt.Sprintf("Image filesize too big. Max size: %d bytes", maxSize),
			}
		} else {
			c.Perf.StartBlock("IMAGE", "Decoding image")
			config, format, err := image.DecodeConfig(img)
			c.Perf.EndBlock()
			if err != nil {
				return SaveImageFileResult{
					ValidationError: "Image type not supported",
				}
			}
			width = config.Width
			height = config.Height
			if width == 0 || height == 0 {
				return SaveImageFileResult{
					ValidationError: "Image has zero size",
				}
			}

			filename = fmt.Sprintf("%s.%s", filepath, format)
			storageFilename := fmt.Sprintf("public/media/%s", filename)
			c.Perf.StartBlock("IMAGE", "Writing image file")
			file, err := os.Create(storageFilename)
			if err != nil {
				return SaveImageFileResult{
					FatalError: oops.New(err, "Failed to create local image file"),
				}
			}
			img.Seek(0, io.SeekStart)
			_, err = io.Copy(file, img)
			if err != nil {
				return SaveImageFileResult{
					FatalError: oops.New(err, "Failed to write image to file"),
				}
			}
			file.Close()
			img.Close()
			c.Perf.EndBlock()
		}
	}
	c.Perf.EndBlock()

	c.Perf.StartBlock("SQL", "Saving image file")
	if filename != "" {
		hasher := sha1.New()
		img.Seek(0, io.SeekStart)
		io.Copy(hasher, img) // NOTE(asaf): Writing to hash.Hash never returns an error according to the docs
		sha1sum := hasher.Sum(nil)
		// TODO(db): Should use insert helper
		imageFile, err := db.QueryOne(c.Context(), dbConn, models.ImageFile{},
			`
			INSERT INTO handmade_imagefile (file, size, sha1sum, protected, width, height)
			VALUES ($1, $2, $3, $4, $5, $6)
			RETURNING $columns
			`,
			filename, header.Size, hex.EncodeToString(sha1sum), false, width, height,
		)
		if err != nil {
			return SaveImageFileResult{
				FatalError: oops.New(err, "Failed to insert image file row"),
			}
		}

		return SaveImageFileResult{
			ImageFile: imageFile.(*models.ImageFile),
		}
	}

	return SaveImageFileResult{}
}
