package playback

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/detector"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/xmlutils"
	v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	"github.com/aws/aws-sdk-go-v2/credentials"
)

type (
	httpBody      []byte
	LoggedRequest struct {
		From     string      `json:"from"`
		URI      string      `json:"URI"`
		Method   string      `json:"method"`
		Payload  httpBody    `json:"payload,omitempty"`
		Response httpBody    `json:"response,omitempty"`
		Query    url.Values  `json:"query"`
		Header   http.Header `json:"headers"`
	}
	Credentials struct {
		AccessKey string
		SecretKey string
	}
	Settings struct {
		Endpoint   string
		Creds      Credentials
		Multiparts map[string]MultipartUpload
		Client     *http.Client
	}
)

func (h *httpBody) UnmarshalJSON(data []byte) error {
	unquoted, err := strconv.Unquote(string(data))
	if err != nil {
		return fmt.Errorf("failed to unquote data: %w", err)
	}
	detect := detector.NewDetector(strings.NewReader(unquoted), xmlutils.DetectXML)
	dataType, err := detect.Detect()
	if err != nil {
		return fmt.Errorf("failed to detect data: %w", err)
	}
	reader := xmlutils.ChooseReader(dataType, detect.RestoredReader())
	*h, err = io.ReadAll(reader)
	if err != nil {
		return fmt.Errorf("failed to unmarshal httpbody: %w", err)
	}

	return nil
}

// Sign replace Authorization header with new Access key id and Signature values.
func Sign(ctx context.Context, r *http.Request, creds Credentials) error {
	credProvider := credentials.NewStaticCredentialsProvider(creds.AccessKey, creds.SecretKey, "")
	awsCred, err := credProvider.Retrieve(ctx)
	if err != nil {
		return err
	}

	authHdr := r.Header.Get(auth.AuthorizationHdr)
	authInfo, err := parseAuthHeader(authHdr)
	if err != nil {
		return err
	}
	newHeader := strings.Replace(authHdr, authInfo["access_key_id"], creds.AccessKey, 1)
	r.Header.Set(auth.AuthorizationHdr, newHeader)

	signer := v4.NewSigner()
	signatureDateTimeStr := r.Header.Get(api.AmzDate)
	signatureDateTime, err := time.Parse("20060102T150405Z", signatureDateTimeStr)
	if err != nil {
		return err
	}

	return signer.SignHTTP(ctx, awsCred, r, r.Header.Get(api.AmzContentSha256), authInfo["service"], authInfo["region"], signatureDateTime)
}

func parseAuthHeader(authHeader string) (map[string]string, error) {
	authInfo := auth.NewRegexpMatcher(auth.AuthorizationFieldRegexp).GetSubmatches(authHeader)
	if len(authInfo) == 0 {
		return nil, errors.New("no matches found")
	}

	return authInfo, nil
}