package agent

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"fmt"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"strings"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/errz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/httpz"
)

const (
	defaultServiceAPIBaseURL = "http://webhook-receiver.flux-system.svc.cluster.local"
)

var (
	kubeProxyAPIPathRegex = regexp.MustCompile("/api/v1/namespaces/[^/]+/services/[^/]+/proxy")
)

type reconcileTrigger interface {
	reconcile(ctx context.Context, webhookPath string, receiverSecret []byte) error
}

type gitRepositoryReconcileTrigger struct {
	baseURL url.URL
	rt      http.RoundTripper
}

func newGitRepositoryReconcileTrigger(cfgURL string, kubeAPIURL *url.URL, kubeAPIRoundTripper http.RoundTripper, defaultRoundTripper http.RoundTripper) (*gitRepositoryReconcileTrigger, error) {
	if kubeProxyAPIPathRegex.MatchString(cfgURL) {
		u := *kubeAPIURL
		u.Path = path.Join(u.Path, cfgURL)
		return &gitRepositoryReconcileTrigger{baseURL: u, rt: kubeAPIRoundTripper}, nil
	} else {
		u, err := url.Parse(cfgURL)
		if err != nil {
			return nil, err
		}
		return &gitRepositoryReconcileTrigger{baseURL: *u, rt: defaultRoundTripper}, nil
	}
}

func (t *gitRepositoryReconcileTrigger) reconcile(ctx context.Context, webhookPath string, receiverSecret []byte) (retErr error) {
	u := t.baseURL
	u.Path = path.Join(u.Path, webhookPath)
	reqBody := time.Now().Format(time.DateTime)
	hashSignature := generateHMACsignature(reqBody, receiverSecret)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), strings.NewReader(reqBody))
	if err != nil {
		return err
	}

	// The flux controller uses the X-Signature header to get the hash signature, which is needed for a
	// generic HMAC implementation. This signature should be prefixed with the hash function
	// (sha1, sha256 or sha512) used to generate the signature, in the following format: <hash-function>=<hash>.
	req.Header[httpz.SignatureHeader] = []string{"sha256=" + hashSignature}

	resp, err := t.rt.RoundTrip(req) //nolint:bodyclose
	if err != nil {
		return err
	}
	defer errz.DiscardAndClose(resp.Body, &retErr)

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("trigger to %q returned status %q", u.String(), resp.Status)
	}
	return nil
}

func generateHMACsignature(message string, secret []byte) string {
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(message))

	return fmt.Sprintf("%x", mac.Sum(nil))
}
