package handler

import (
	"context"
	"errors"
	"io"
	"net/http"
	"strconv"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"github.com/valyala/fasthttp"
	"go.uber.org/zap"
)

// max bytes needed to detect content type according to http.DetectContentType docs.
const sizeToDetectType = 512

const (
	hdrObjectID    = "X-Object-Id"
	hdrOwnerID     = "X-Owner-Id"
	hdrContainerID = "X-Container-Id"
)

func (h *Handler) headObject(ctx context.Context, req request, objectAddress oid.Address) {
	var start = time.Now()

	btoken := bearerToken(ctx)

	prm := PrmObjectHead{
		PrmAuth: PrmAuth{
			BearerToken: btoken,
		},
		Address: objectAddress,
	}

	obj, err := h.frostfs.HeadObject(ctx, prm)
	if err != nil {
		req.handleFrostFSErr(err, start)
		return
	}

	req.Response.Header.Set(fasthttp.HeaderContentLength, strconv.FormatUint(obj.PayloadSize(), 10))
	var (
		contentType string
		filename    string
		filepath    string
	)
	for _, attr := range obj.Attributes() {
		key := attr.Key()
		val := attr.Value()
		if !isValidToken(key) || !isValidValue(val) {
			continue
		}

		key = utils.BackwardTransformIfSystem(key)

		req.Response.Header.Set(utils.UserAttributeHeaderPrefix+key, val)
		switch key {
		case object.AttributeTimestamp:
			value, err := strconv.ParseInt(val, 10, 64)
			if err != nil {
				req.log.Info(logs.CouldntParseCreationDate,
					zap.String("key", key),
					zap.String("val", val),
					zap.Error(err),
					logs.TagField(logs.TagDatapath))
				continue
			}
			req.Response.Header.Set(fasthttp.HeaderLastModified, time.Unix(value, 0).UTC().Format(http.TimeFormat))
		case object.AttributeContentType:
			contentType = val
		case object.AttributeFilePath:
			filepath = val
		case object.AttributeFileName:
			filename = val
		}
	}
	if filename == "" {
		filename = filepath
	}

	idsToResponse(&req.Response, obj)

	if len(contentType) == 0 {
		contentType, _, err = readContentType(obj.PayloadSize(), func(sz uint64) (io.Reader, error) {
			prmRange := PrmObjectRange{
				PrmAuth: PrmAuth{
					BearerToken: btoken,
				},
				Address:      objectAddress,
				PayloadRange: [2]uint64{0, sz},
			}

			return h.frostfs.RangeObject(ctx, prmRange)
		}, filename)
		if err != nil && err != io.EOF {
			req.handleFrostFSErr(err, start)
			return
		}
	}
	req.SetContentType(contentType)
}

func idsToResponse(resp *fasthttp.Response, obj *object.Object) {
	objID, _ := obj.ID()
	cnrID, _ := obj.ContainerID()
	resp.Header.Set(hdrObjectID, objID.String())
	resp.Header.Set(hdrOwnerID, obj.OwnerID().String())
	resp.Header.Set(hdrContainerID, cnrID.String())
}

// HeadByAddressOrBucketName handles head requests using simple cid/oid or bucketname/key format.
func (h *Handler) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) {
	cidParam, _ := c.UserValue("cid").(string)
	oidParam, _ := c.UserValue("oid").(string)

	ctx := utils.GetContextFromRequest(c)
	log := utils.GetReqLogOrDefault(ctx, h.log).With(
		zap.String("cid", cidParam),
		zap.String("oid", oidParam),
	)

	bktInfo, err := h.getBucketInfo(ctx, cidParam, log)
	if err != nil {
		logAndSendBucketError(c, log, err, logs.TagExternalStorage)
		return
	}
	checkS3Err := h.tree.CheckSettingsNodeExists(ctx, bktInfo)
	if checkS3Err != nil && !errors.Is(checkS3Err, layer.ErrNodeNotFound) {
		logAndSendBucketError(c, log, checkS3Err, logs.TagExternalStorageTree)
		return
	}

	req := h.newRequest(c, log)

	var objID oid.ID
	if checkS3Err == nil {
		h.byS3Path(ctx, req, bktInfo.CID, oidParam, h.headObject)
	} else if err = objID.DecodeString(oidParam); err == nil {
		h.byNativeAddress(ctx, req, bktInfo.CID, objID, h.headObject)
	} else {
		logAndSendBucketError(c, log, checkS3Err, logs.TagExternalStorageTree)
		return
	}
}

// HeadByAttribute handles attribute-based head requests.
func (h *Handler) HeadByAttribute(c *fasthttp.RequestCtx) {
	h.byAttribute(c, h.headObject)
}