// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"io"
	"net/http"
	"strings"

	actions_model "code.gitea.io/gitea/models/actions"
	"code.gitea.io/gitea/modules/httplib"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/storage"
	"code.gitea.io/gitea/services/context"
)

type tagType string

// BuildSignature builds a hmac signature for the input values.
// "tag" is an internal pre-defined static string to distinguish the signatures for different purpose.
func BuildSignature(tag tagType, vals ...string) []byte {
	m := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
	_, _ = io.WriteString(m, string(tag))
	var buf8 [8]byte
	for _, v := range vals {
		binary.LittleEndian.PutUint64(buf8[:], uint64(len(v)))
		_, _ = m.Write(buf8[:])
		_, _ = io.WriteString(m, v)
	}
	return m.Sum(nil)
}

// IsArtifactV4 detects whether the artifact is likely from v4.
// V4 backend stores the files as a single combined zip file per artifact, and ensures ContentEncoding contains a slash
// (otherwise this uses application/zip instead of the custom mime type), which is not the case for the old backend.
func IsArtifactV4(art *actions_model.ActionArtifact) bool {
	return strings.Contains(art.ContentEncodingOrType, "/")
}

func GetArtifactV4ServeDirectURL(art *actions_model.ActionArtifact, method string) (string, error) {
	contentType := art.ContentEncodingOrType
	u, err := storage.ActionsArtifacts.ServeDirectURL(art.StoragePath, art.ArtifactPath, method, &storage.ServeDirectOptions{ContentType: contentType})
	if err != nil {
		return "", err
	}
	return u.String(), nil
}

func DownloadArtifactV4ServeDirect(ctx *context.Base, art *actions_model.ActionArtifact) bool {
	if !setting.Actions.ArtifactStorage.ServeDirect() {
		return false
	}
	u, err := GetArtifactV4ServeDirectURL(art, ctx.Req.Method)
	if err != nil {
		log.Error("GetArtifactV4ServeDirectURL: %v", err)
		return false
	}
	ctx.Redirect(u, http.StatusFound)
	return true
}

func DownloadArtifactV4ReadStorage(ctx *context.Base, art *actions_model.ActionArtifact) error {
	f, err := storage.ActionsArtifacts.Open(art.StoragePath)
	if err != nil {
		return err
	}
	defer f.Close()
	httplib.ServeUserContentByFile(ctx.Req, ctx.Resp, f, httplib.ServeHeaderOptions{
		Filename:    art.ArtifactPath,
		ContentType: art.ContentEncodingOrType, // v4 guarantees that the field is Content-Type
	})
	return nil
}

func DownloadArtifactV4(ctx *context.Base, art *actions_model.ActionArtifact) error {
	if DownloadArtifactV4ServeDirect(ctx, art) {
		return nil
	}
	return DownloadArtifactV4ReadStorage(ctx, art)
}
