// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.

package check

import (
	"context"
	"encoding/json"
	"fmt"
	"regexp"
	"time"

	"github.com/harness/gitness/gitrpc"
	"github.com/harness/gitness/internal/api/usererror"
	"github.com/harness/gitness/internal/auth"
	"github.com/harness/gitness/types"
	"github.com/harness/gitness/types/enum"
)

type ReportInput struct {
	CheckUID string             `json:"check_uid"`
	Status   enum.CheckStatus   `json:"status"`
	Summary  string             `json:"summary"`
	Link     string             `json:"link"`
	Payload  types.CheckPayload `json:"payload"`
}

var regexpCheckUID = "^[a-zA-Z_][0-9a-zA-Z-_.$]{0,127}$"
var matcherCheckUID = regexp.MustCompile(regexpCheckUID)

// Validate validates and sanitizes the ReportInput data.
func (in *ReportInput) Validate() error {
	if in.CheckUID == "" {
		return usererror.BadRequest("Status check UID is missing")
	}

	if !matcherCheckUID.MatchString(in.CheckUID) {
		return usererror.BadRequestf("Status check UID must match the regular expression: %s", regexpCheckUID)
	}

	_, ok := in.Status.Sanitize()
	if !ok {
		return usererror.BadRequest("Invalid value provided for status check status")
	}

	payloadKind, ok := in.Payload.Kind.Sanitize()
	if !ok {
		return usererror.BadRequest("Invalid value provided for the payload type")
	}
	in.Payload.Kind = payloadKind

	//nolint:gocritic // more values to follow on the enum (we want linter warning in case it is missed)
	switch in.Payload.Kind {
	case enum.CheckPayloadKindExternal:
		// the default external type does not support payload: clear it here
		in.Payload.Version = ""
		in.Payload.Data = []byte{'{', '}'}

		if in.Link == "" { // the link is mandatory for the external
			return usererror.BadRequest("Link is missing")
		}
	}

	return nil
}

// Report modifies an existing or creates a new (if none yet exists) status check report for a specific commit.
func (c *Controller) Report(
	ctx context.Context,
	session *auth.Session,
	repoRef string,
	commitSHA string,
	in *ReportInput,
	metadata map[string]string,
) (*types.Check, error) {
	repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionCommitCheckReport)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire access access to repo: %w", err)
	}

	if errValidate := in.Validate(); errValidate != nil {
		return nil, errValidate
	}

	if !gitrpc.ValidateCommitSHA(commitSHA) {
		return nil, usererror.BadRequest("invalid commit SHA provided")
	}

	_, err = c.gitRPCClient.GetCommit(ctx, &gitrpc.GetCommitParams{
		ReadParams: gitrpc.ReadParams{RepoUID: repo.GitUID},
		SHA:        commitSHA,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to commit sha=%s: %w", commitSHA, err)
	}

	now := time.Now().UnixMilli()

	metadataJSON, _ := json.Marshal(metadata)

	statusCheckReport := &types.Check{
		CreatedBy:  session.Principal.ID,
		Created:    now,
		Updated:    now,
		RepoID:     repo.ID,
		CommitSHA:  commitSHA,
		UID:        in.CheckUID,
		Status:     in.Status,
		Summary:    in.Summary,
		Link:       in.Link,
		Payload:    in.Payload,
		Metadata:   metadataJSON,
		ReportedBy: *session.Principal.ToPrincipalInfo(),
	}

	err = c.checkStore.Upsert(ctx, statusCheckReport)
	if err != nil {
		return nil, fmt.Errorf("failed to upsert status check result for repo=%s: %w", repo.UID, err)
	}

	return statusCheckReport, nil
}