package controlplane

import (
	"fmt"
	"strings"

	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

const ApplyAllSourceRevisionAdvancePlanCompose ApplyAllSourceRevisionAdvancePlanErrorStage = "compose"

type applyAllSourceRevisionAdvanceTuple struct {
	plan    []normalizedApplyAllSourceRevisionAdvance
	targets map[string]*packs.SourceBackedPack
}

// The source and installed-compatibility tuples are distinct resolver-minted
// roles. Callers cannot accidentally swap or duplicate them at a capability
// boundary even though both roles carry the same tuple shape.
type applyAllSourceRevisionAdvanceSourceTuple struct {
	applyAllSourceRevisionAdvanceTuple
	sourceOptions    packs.PackSourceOptions
	hasSourceOptions bool
}

type applyAllSourceRevisionAdvanceInstalledCompatibilityTuple struct {
	applyAllSourceRevisionAdvanceTuple
	activeRuntimeIdentity *applyAllSourceRevisionAdvanceActiveRuntimeIdentity
}

type applyAllSourceRevisionAdvanceSourceResolution struct {
	targets       map[string]*packs.SourceBackedPack
	sourceOptions packs.PackSourceOptions
}

type applyAllSourceRevisionSourceValidator func(
	string,
	packs.PackSourceOptions,
	packs.ComponentKindRegistrationSet,
) packs.PackValidationReport

var validateApplyAllSourceRevisionSource applyAllSourceRevisionSourceValidator = packs.ValidatePackSourceWithComponentKindRegistrationSet

func applyAllSourceRevisionAdvancePackSourceOptions(
	plan []normalizedApplyAllSourceRevisionAdvance,
) packs.PackSourceOptions {
	return applyAllSourceRevisionAdvancePackSourceOptionsFromCanonicalRevisions(
		applyAllSourceRevisionAdvanceTargetRevisions(plan),
	)
}

func applyAllSourceRevisionAdvanceTargetRevisions(
	plan []normalizedApplyAllSourceRevisionAdvance,
) map[string]string {
	revisions := make(map[string]string, len(plan))
	for _, advance := range plan {
		revisions[strings.ToLower(advance.URI)] = advance.ToRevision
	}
	return revisions
}

func applyAllSourceRevisionAdvancePackSourceOptionsAtRevisions(
	plan []normalizedApplyAllSourceRevisionAdvance,
	revisions map[string]string,
) (packs.PackSourceOptions, error) {
	return applyAllSourceRevisionAdvancePackSourceOptionsAtRevisionsWithBase(
		plan, revisions, packs.DefaultPackSourceOptions(),
	)
}

func applyAllSourceRevisionAdvancePackSourceOptionsAtRevisionsWithBase(
	plan []normalizedApplyAllSourceRevisionAdvance,
	revisions map[string]string,
	base packs.PackSourceOptions,
) (packs.PackSourceOptions, error) {
	normalized, err := normalizeApplyAllSourceRevisionAdvanceRevisions(plan, revisions)
	if err != nil {
		return packs.PackSourceOptions{}, err
	}
	sourceOptions := base
	sourceOptions.OfficialRepoSourceRevisions = cloneSourceRevisionMap(base.OfficialRepoSourceRevisions)
	sourceOptions.ReleaseRepoSources = cloneReleaseRepoSourceMap(base.ReleaseRepoSources)
	for _, advance := range plan {
		revision := normalized[strings.ToLower(advance.URI)]
		switch advance.Kind {
		case string(packs.PackSourceKindOfficialRepo):
			if sourceOptions.OfficialRepoSourceRevisions == nil {
				sourceOptions.OfficialRepoSourceRevisions = make(map[string]string, len(plan))
			}
			sourceOptions.OfficialRepoSourceRevisions[strings.ToLower(advance.URI)] = revision
		case string(packs.PackSourceKindReleaseRepo):
			authority, ok := sourceOptions.ReleaseRepoSources[advance.URI]
			if !ok || strings.TrimSpace(authority.Root) == "" {
				return packs.PackSourceOptions{}, applyAllSourceRevisionAdvancePlanError(
					ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name,
					fmt.Errorf("release_repo source %q has no exact operation authority", advance.URI),
				)
			}
			authority.Revision = revision
			sourceOptions.ReleaseRepoSources[advance.URI] = authority
		}
	}
	return sourceOptions, nil
}

func applyAllSourceRevisionAdvancePackSourceOptionsFromCanonicalRevisions(
	revisions map[string]string,
) packs.PackSourceOptions {
	sourceOptions := packs.DefaultPackSourceOptions()
	if sourceOptions.OfficialRepoSourceRevisions == nil {
		sourceOptions.OfficialRepoSourceRevisions = make(map[string]string, len(revisions))
	}
	for name, revision := range revisions {
		sourceOptions.OfficialRepoSourceRevisions[name] = revision
	}
	return sourceOptions
}

func cloneSourceRevisionMap(values map[string]string) map[string]string {
	if values == nil {
		return nil
	}
	cloned := make(map[string]string, len(values))
	for key, value := range values {
		cloned[key] = value
	}
	return cloned
}

func cloneReleaseRepoSourceMap(values map[string]packs.ReleaseRepoSource) map[string]packs.ReleaseRepoSource {
	if values == nil {
		return nil
	}
	cloned := make(map[string]packs.ReleaseRepoSource, len(values))
	for key, value := range values {
		cloned[key] = value
	}
	return cloned
}

func applyAllSourceRevisionFromOptions(
	advance normalizedApplyAllSourceRevisionAdvance,
	options packs.PackSourceOptions,
) string {
	switch advance.Kind {
	case string(packs.PackSourceKindReleaseRepo):
		return strings.TrimSpace(options.ReleaseRepoSources[advance.URI].Revision)
	default:
		return strings.TrimSpace(options.OfficialRepoSourceRevisions[strings.ToLower(advance.URI)])
	}
}

func normalizeApplyAllSourceRevisionAdvanceRevisions(
	plan []normalizedApplyAllSourceRevisionAdvance,
	revisions map[string]string,
) (map[string]string, error) {
	if len(revisions) != len(plan) {
		return nil, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("explicit source revision map has %d entries for %d planned packs", len(revisions), len(plan)),
		)
	}
	normalized := make(map[string]string, len(plan))
	for _, advance := range plan {
		name := strings.ToLower(advance.URI)
		revision, ok := revisions[name]
		if !ok {
			return nil, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, fmt.Errorf("canonical explicit source revision is missing"),
			)
		}
		revision = strings.TrimSpace(revision)
		if revision == "" {
			return nil, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, fmt.Errorf("explicit source revision is empty"),
			)
		}
		normalized[name] = revision
	}
	return normalized, nil
}

func resolveApplyAllSourceRevisionAdvanceSourcesAtRevisions(
	plan []normalizedApplyAllSourceRevisionAdvance,
	revisions map[string]string,
	registrations packs.ComponentKindRegistrationSet,
) (applyAllSourceRevisionAdvanceSourceResolution, error) {
	return resolveApplyAllSourceRevisionAdvanceSourcesAtRevisionsWithBase(
		plan, revisions, registrations, packs.DefaultPackSourceOptions(),
	)
}

func resolveApplyAllSourceRevisionAdvanceSourcesAtRevisionsWithBase(
	plan []normalizedApplyAllSourceRevisionAdvance,
	revisions map[string]string,
	registrations packs.ComponentKindRegistrationSet,
	base packs.PackSourceOptions,
) (applyAllSourceRevisionAdvanceSourceResolution, error) {
	sourceOptions, err := applyAllSourceRevisionAdvancePackSourceOptionsAtRevisionsWithBase(plan, revisions, base)
	if err != nil {
		return applyAllSourceRevisionAdvanceSourceResolution{}, err
	}
	sourceOptions = packSourceOptionsForRequestedSourceValidation(sourceOptions)
	targets := make(map[string]*packs.SourceBackedPack, len(plan))
	for _, advance := range plan {
		report := validateApplyAllSourceRevisionSource(advance.Kind+":"+advance.URI, sourceOptions, registrations)
		if err := packValidationReportError("apply-all source revision advance", report); err != nil {
			return applyAllSourceRevisionAdvanceSourceResolution{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, err,
			)
		}
		target, err := packs.NewSourceBackedPack(
			report.Source,
			packs.WithComponentKindRegistrationSet(registrations),
			packs.WithFlowActionCallRuntimeFromPackSourceOptions(sourceOptions),
			packs.WithConnectorPushValidationSchemaResolverForSource(report.Source, sourceOptions),
		)
		if err != nil {
			return applyAllSourceRevisionAdvanceSourceResolution{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, err,
			)
		}
		expected := advance
		expected.ToRevision = applyAllSourceRevisionFromOptions(advance, sourceOptions)
		if err := validateApplyAllSourceRevisionAdvanceTarget(expected, target); err != nil {
			return applyAllSourceRevisionAdvanceSourceResolution{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, err,
			)
		}
		targets[strings.ToLower(advance.Name)] = target
	}
	return applyAllSourceRevisionAdvanceSourceResolution{targets: targets, sourceOptions: sourceOptions}, nil
}

// resolveApplyAllSourceRevisionAdvanceSourceTuple pins the complete requested
// revision set before the first source lookup and orders the resolved targets.
func resolveApplyAllSourceRevisionAdvanceSourceTuple(
	plan []normalizedApplyAllSourceRevisionAdvance,
	registrations packs.ComponentKindRegistrationSet,
) (applyAllSourceRevisionAdvanceSourceTuple, error) {
	return resolveApplyAllSourceRevisionAdvanceSourceTupleWithBase(
		plan, registrations, packs.DefaultPackSourceOptions(),
	)
}

func resolveApplyAllSourceRevisionAdvanceSourceTupleWithBase(
	plan []normalizedApplyAllSourceRevisionAdvance,
	registrations packs.ComponentKindRegistrationSet,
	base packs.PackSourceOptions,
) (applyAllSourceRevisionAdvanceSourceTuple, error) {
	if len(plan) == 0 {
		return applyAllSourceRevisionAdvanceSourceTuple{
			applyAllSourceRevisionAdvanceTuple: applyAllSourceRevisionAdvanceTuple{
				plan:    []normalizedApplyAllSourceRevisionAdvance{},
				targets: map[string]*packs.SourceBackedPack{},
			},
			sourceOptions:    base,
			hasSourceOptions: true,
		}, nil
	}

	resolution, err := resolveApplyAllSourceRevisionAdvanceSourcesAtRevisionsWithBase(
		plan, applyAllSourceRevisionAdvanceTargetRevisions(plan), registrations, base,
	)
	if err != nil {
		return applyAllSourceRevisionAdvanceSourceTuple{}, err
	}

	ordered, err := orderApplyAllSourceRevisionAdvanceTuple(plan, resolution.targets)
	if err != nil {
		return applyAllSourceRevisionAdvanceSourceTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("order target tuple: %w", err),
		)
	}
	return applyAllSourceRevisionAdvanceSourceTuple{
		applyAllSourceRevisionAdvanceTuple: applyAllSourceRevisionAdvanceTuple{plan: ordered, targets: resolution.targets},
		sourceOptions:                      resolution.sourceOptions,
		hasSourceOptions:                   true,
	}, nil
}
