package rpc

import (
	"context"
	"io"
	"log/slog"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/grpctool"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/pkg/agentcfg"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/tool/retry"
	"google.golang.org/grpc"
)

type ConfigurationData struct {
	CommitID string
	Config   *agentcfg.AgentConfiguration
}

type ConfigurationWatcher struct {
	Log                *slog.Logger
	Client             AgentConfigurationClient
	PollConfig         retry.PollConfig
	ConfigPreProcessor func(ConfigurationData) error
}

func (w *ConfigurationWatcher) Watch(ctx context.Context, callback func(context.Context, ConfigurationData)) {
	var lastProcessedCommitID string
	_ = retry.PollWithBackoff(ctx, w.PollConfig, func(ctx context.Context) (error, retry.AttemptResult) { //nolint:staticcheck
		ctx, cancel := context.WithCancel(ctx)
		defer cancel() // ensure streaming call is canceled
		res, err := w.Client.GetConfiguration(ctx, &ConfigurationRequest{
			CommitId: lastProcessedCommitID,
		}, grpc.WaitForReady(true))
		if err != nil {
			if !grpctool.RequestCanceledOrTimedOut(err) {
				w.Log.Warn("GetConfiguration failed", logz.Error(err))
			}
			return nil, retry.Backoff
		}
		for {
			config, err := res.Recv()
			if err != nil {
				switch {
				case err == io.EOF: //nolint:errorlint
					return nil, retry.ContinueImmediately // immediately reconnect after a clean close
				case grpctool.RequestCanceledOrTimedOut(err):
				default:
					w.Log.Warn("GetConfiguration.Recv failed", logz.Error(err))
				}
				return nil, retry.Backoff
			}
			data := ConfigurationData{
				CommitID: config.CommitId,
				Config:   config.Configuration,
			}
			err = w.ConfigPreProcessor(data)
			if err != nil {
				w.Log.Error("Failed to preprocess configuration", logz.Error(err))
				continue
			}
			callback(ctx, data)
			lastProcessedCommitID = config.CommitId
		}
	})
}
