package admin

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

const maxAssetSizeBytes = 12 * 1024 * 1024

var safeAssetNamePattern = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)

type AssetUploader struct {
	store     *Store
	assetsDir string
}

func NewAssetUploader(store *Store, assetsDir string) *AssetUploader {
	return &AssetUploader{store: store, assetsDir: assetsDir}
}

func (u *AssetUploader) Upload(ctx context.Context, file multipart.File, header *multipart.FileHeader) (Asset, error) {
	if header.Size > maxAssetSizeBytes {
		return Asset{}, fmt.Errorf("asset is too large: max %d bytes", maxAssetSizeBytes)
	}

	data, err := io.ReadAll(io.LimitReader(file, maxAssetSizeBytes+1))
	if err != nil {
		return Asset{}, fmt.Errorf("read asset: %w", err)
	}
	if int64(len(data)) > maxAssetSizeBytes {
		return Asset{}, fmt.Errorf("asset is too large: max %d bytes", maxAssetSizeBytes)
	}

	mimeType := http.DetectContentType(data)
	if !strings.HasPrefix(mimeType, "image/") {
		return Asset{}, fmt.Errorf("unsupported asset type: %s", mimeType)
	}

	sum := sha256.Sum256(data)
	sha := hex.EncodeToString(sum[:])
	name := assetFilename(header.Filename, sha)
	if err := os.MkdirAll(u.assetsDir, 0o755); err != nil {
		return Asset{}, fmt.Errorf("create assets dir: %w", err)
	}

	if err := os.WriteFile(filepath.Join(u.assetsDir, name), data, 0o644); err != nil {
		return Asset{}, fmt.Errorf("write asset: %w", err)
	}

	asset := Asset{
		Path:         "/assets/" + name,
		OriginalName: header.Filename,
		MimeType:     mimeType,
		SizeBytes:    int64(len(data)),
		SHA256:       sha,
	}
	return u.store.CreateAsset(ctx, asset)
}

func assetFilename(original string, sha string) string {
	ext := strings.ToLower(filepath.Ext(original))
	base := strings.TrimSuffix(filepath.Base(original), filepath.Ext(original))
	base = strings.Trim(safeAssetNamePattern.ReplaceAllString(base, "-"), "-._")
	if base == "" {
		base = "image"
	}
	if ext == "" {
		ext = ".bin"
	}
	return fmt.Sprintf("%s-%s%s", base, sha[:12], ext)
}
