package agent

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/module/modagent"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/errz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/logz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/retry"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/pkg/agentcfg"
	"go.uber.org/zap"
)

const (
	policiesPollInterval      = 30 * time.Second
	policiesPollInitBackoff   = 10 * time.Second
	policiesPollMaxBackoff    = 5 * time.Minute
	policiesPollResetDuration = 10 * time.Minute
	policiesPollBackoffFactor = 2.0
	policiesPollJitter        = 1.0
)

type configurationToUpdateData struct {
	agentId                 int64
	containerScanningConfig *agentcfg.ContainerScanningCF
}

type securityPoliciesWorker struct {
	log     *zap.Logger
	api     modagent.Api
	updater chan<- configurationToUpdateData
}

type SecurityPolicyConfiguration struct {
	Cadence    string    `json:"cadence"`
	Namespaces []string  `json:"namespaces"`
	UpdatedAt  time.Time `json:"updated_at"`
}

type getSecurityPoliciesResponse struct {
	Policies []*SecurityPolicyConfiguration `json:"configurations"`
}

func (w *securityPoliciesWorker) Run(ctx context.Context) {
	_ = retry.PollWithBackoff(ctx, securityPoliciesPollConfig(), func(ctx context.Context) (error, retry.AttemptResult) {
		response, err := w.requestPolicy(ctx)
		if err != nil {
			w.log.Error("Error checking security policies", logz.Error(err))
			return nil, retry.Backoff
		}

		agentId, err := w.api.GetAgentId(ctx)
		if err != nil {
			w.log.Error("Could not retrieve agent information", logz.Error(err))
			return nil, retry.Backoff
		}

		updateData := configurationToUpdateData{
			agentId: agentId,
		}

		if len(response.Policies) > 0 {
			updateData.containerScanningConfig = convertPolicy(response.Policies[0])
		}

		select {
		case w.updater <- updateData:
			return nil, retry.Continue
		case <-ctx.Done():
			return nil, retry.Done
		}
	})
}

func (w *securityPoliciesWorker) requestPolicy(ctx context.Context) (policiesResponse *getSecurityPoliciesResponse, retError error) {
	resp, err := w.api.MakeGitLabRequest(
		ctx,
		"/policies_configuration",
		modagent.WithRequestMethod(http.MethodGet),
	)
	if err != nil {
		return nil, fmt.Errorf("could not retrieve security policies: %w", err)
	}

	defer errz.SafeClose(resp.Body, &retError)
	switch resp.StatusCode {
	case http.StatusNotFound, http.StatusPaymentRequired:
		// The project does not have an ultimate license.
		// Treat this the same as an empty policy response.
		return new(getSecurityPoliciesResponse), nil
	case http.StatusOK:
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("error reading response body: %w", err)
		}

		securityPoliciesResponse := new(getSecurityPoliciesResponse)
		err = json.Unmarshal(body, securityPoliciesResponse)
		if err != nil {
			return nil, fmt.Errorf("error parsing response body: %w", err)
		}

		return securityPoliciesResponse, nil
	default:
		return nil, fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode)
	}
}

func convertPolicy(policy *SecurityPolicyConfiguration) *agentcfg.ContainerScanningCF {
	return &agentcfg.ContainerScanningCF{
		Cadence:             policy.Cadence,
		VulnerabilityReport: &agentcfg.VulnerabilityReport{Namespaces: policy.Namespaces},
	}
}

func securityPoliciesPollConfig() retry.PollConfig {
	return retry.NewPollConfigFactory(policiesPollInterval, retry.NewExponentialBackoffFactory(
		policiesPollInitBackoff,
		policiesPollMaxBackoff,
		policiesPollResetDuration,
		policiesPollBackoffFactor,
		policiesPollJitter,
	))()
}
