package kernelimageaction

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/clients/fidcli"
	"github.com/tidbcloud/devops/pkg/service/kernelimage/repo"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

const outputSourceDigestQueries opent.OutputName = "kernel_image_source_digest_queries"

const sourceDigestQueryFailedCode = "SOURCE_DIGEST_QUERY_FAILED"

var ActionResolveKernelImageDigest = opent.ActionMetadata{
	Name: "ResolveKernelImageDigest",
	Type: opent.ActionTypeAuto,
}

type sourceDigestQueryState struct {
	ArtifactUID string                        `json:"artifact_uid"`
	Query       fidcli.ImageAvailabilityQuery `json:"query"`
}

type resolveKernelImageDigest struct {
	actions.BaseAction
	fid      fidcli.Client
	releases repo.Repo
}

func NewResolveKernelImageDigest(comps *actions.Components) actions.AutoAction {
	return &resolveKernelImageDigest{
		fid:      comps.FIDCli,
		releases: repo.NewRepo(comps.DBProvider),
	}
}

func (a *resolveKernelImageDigest) Metadata() opent.ActionMetadata {
	return ActionResolveKernelImageDigest
}

func (a *resolveKernelImageDigest) PreCheck(_ context.Context, instance *opent.Instance) error {
	for _, name := range []opent.ParamName{
		opent.ParamKernelImageArtifactUID,
		opent.ParamKernelImageSourceRepository,
		opent.ParamKernelImageSourceTag,
		opent.ParamKernelImageDeliveryCorrelationID,
	} {
		value, ok := instance.GetParam(name)
		if !ok || strings.TrimSpace(value) == "" {
			return fmt.Errorf("param %s is required", name)
		}
	}
	return nil
}

func (a *resolveKernelImageDigest) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	batchUID := instance.Params[opent.ParamKernelImageDeliveryCorrelationID]
	releases, err := a.releases.ListReleasesByDeliveryBatchUID(ctx, batchUID)
	if err != nil {
		return nil, fmt.Errorf("get kernel image delivery batch: %w", err)
	}
	if err := validateBatchOperation(releases, instance.ID); err != nil {
		return nil, err
	}

	states := make([]sourceDigestQueryState, 0, len(releases))
	for _, release := range releases {
		if release.SourceDigest != "" {
			continue
		}
		imageRef := release.SourceRepository + ":" + release.SourceTag
		query, err := a.fid.StartImageAvailabilityQuery(ctx, imageRef)
		if err != nil {
			return nil, fmt.Errorf("start source image digest query for %s: %w", imageRef, err)
		}
		if query == nil || query.WorkflowRunID <= 0 || strings.TrimSpace(query.ImageRef) == "" {
			return nil, fmt.Errorf("start source image digest query for %s: workflow handle is incomplete", imageRef)
		}
		states = append(states, sourceDigestQueryState{ArtifactUID: release.ArtifactUID, Query: *query})
	}
	if len(states) == 0 {
		return actions.SucceededActionResult(nil), nil
	}
	encoded, err := json.Marshal(states)
	if err != nil {
		return nil, fmt.Errorf("marshal source image digest queries: %w", err)
	}
	return actions.RunningActionResult(
		fmt.Sprintf("querying source image digests for %d images", len(states)),
		map[opent.OutputName]string{outputSourceDigestQueries: string(encoded)},
	), nil
}

func (a *resolveKernelImageDigest) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	states, err := sourceDigestQueriesFromInstance(instance)
	if err != nil {
		return nil, err
	}
	batchUID := instance.Params[opent.ParamKernelImageDeliveryCorrelationID]
	allCompleted := true
	for _, state := range states {
		availability, completed, err := a.fid.GetImageAvailabilityQueryResult(ctx, state.Query)
		if err != nil {
			return nil, fmt.Errorf("get source image digest query result for %s: %w", state.Query.ImageRef, err)
		}
		if !completed {
			allCompleted = false
			continue
		}
		if availability == nil {
			return nil, fmt.Errorf("source image digest query for %s completed without a result", state.Query.ImageRef)
		}
		if availability.Status != fidcli.ImageAvailabilityStatusAvailable ||
			!strings.HasPrefix(availability.Digest, "sha256:") {
			message := fmt.Sprintf(
				"source image digest query for %s returned %s: %s",
				state.Query.ImageRef,
				availability.Status,
				availability.ErrorMessage,
			)
			if _, markErr := a.releases.MarkBatchSyncFailed(
				ctx,
				batchUID,
				instance.ID,
				sourceDigestQueryFailedCode,
				message,
			); markErr != nil {
				return nil, fmt.Errorf("mark source image digest query failed: %w", markErr)
			}
			return actions.FailedActionResult(fmt.Errorf("%s", message)), nil
		}
		if _, err := a.releases.SetReleaseSourceDigest(
			ctx,
			state.ArtifactUID,
			instance.ID,
			availability.Digest,
		); err != nil {
			return nil, fmt.Errorf("save source image digest for %s: %w", state.Query.ImageRef, err)
		}
	}
	if !allCompleted {
		return actions.RunningActionResult("waiting for source image digest queries to complete", nil), nil
	}
	return actions.SucceededActionResult(nil), nil
}

func (a *resolveKernelImageDigest) OnCancel(_ context.Context, _ *opent.Instance) (bool, error) {
	return false, nil
}

func sourceDigestQueriesFromInstance(instance *opent.Instance) ([]sourceDigestQueryState, error) {
	encoded := strings.TrimSpace(instance.Outputs[outputSourceDigestQueries])
	if encoded == "" {
		return nil, fmt.Errorf("source image digest query outputs are missing")
	}
	states := []sourceDigestQueryState{}
	if err := json.Unmarshal([]byte(encoded), &states); err != nil {
		return nil, fmt.Errorf("unmarshal source image digest queries: %w", err)
	}
	if len(states) == 0 {
		return nil, fmt.Errorf("source image digest queries are empty")
	}
	for _, state := range states {
		if strings.TrimSpace(state.ArtifactUID) == "" || state.Query.WorkflowRunID <= 0 ||
			strings.TrimSpace(state.Query.ImageRef) == "" {
			return nil, fmt.Errorf("source image digest query output is incomplete")
		}
	}
	return states, nil
}
