package pipeline

import (
	"fmt"
	"sort"
	"strings"
)

type checkpointFingerprintComponent struct {
	scope  string
	module any
}

// CheckpointFingerprints returns a defensive copy of the stable semantic
// identities contributed by the pipeline's prepared modules and validators.
func (p *PreparedPipeline) CheckpointFingerprints() []CheckpointFingerprint {
	if p == nil {
		return nil
	}
	return append([]CheckpointFingerprint(nil), p.checkpointFingerprints...)
}

func collectPreparedCheckpointFingerprints(prepared *PreparedPipeline) ([]CheckpointFingerprint, error) {
	components := []checkpointFingerprintComponent{
		{scope: "input:" + prepared.resolved.Input.Module, module: prepared.input},
		{scope: "chunk:" + prepared.resolved.Chunk.Module, module: prepared.chunker},
	}
	components = appendValidatorFingerprintComponents(components, "chunk:"+prepared.resolved.Chunk.Module, prepared.chunkValidators)
	for _, step := range prepared.Steps {
		for _, lane := range step.lanes {
			laneScope := func(stage ModuleStage, moduleKey string) string {
				return string(stage) + ":" + lane.resolved.ID + ":" + moduleKey
			}
			components = append(components, checkpointFingerprintComponent{scope: laneScope(StageExtract, lane.resolved.Extract.Module), module: lane.typed.extractor})
			components = appendValidatorFingerprintComponents(components, laneScope(StageExtract, lane.resolved.Extract.Module), lane.extractValidators)
			components = append(components, checkpointFingerprintComponent{scope: laneScope(StageMerge, lane.resolved.Merge.Module), module: lane.typed.merger})
			components = appendValidatorFingerprintComponents(components, laneScope(StageMerge, lane.resolved.Merge.Module), lane.mergeValidators)
			components = append(components, checkpointFingerprintComponent{scope: laneScope(StageNormalize, lane.resolved.Normalize.Module), module: lane.typed.normalizer})
			components = appendValidatorFingerprintComponents(components, laneScope(StageNormalize, lane.resolved.Normalize.Module), lane.normalizeValidators)
		}
	}
	components = append(components, checkpointFingerprintComponent{scope: "output:" + prepared.resolved.Output.Module, module: prepared.output})

	var values []CheckpointFingerprint
	seen := make(map[string]struct{})
	for _, item := range components {
		provider, ok := item.module.(CheckpointFingerprintProvider)
		if !ok {
			continue
		}
		for _, fingerprint := range provider.CheckpointFingerprints() {
			name := strings.TrimSpace(fingerprint.Name)
			value := strings.TrimSpace(fingerprint.Value)
			if name == "" || value == "" {
				return nil, fmt.Errorf("pipeline %q checkpoint fingerprint for %s must have a non-empty name and value", prepared.resolved.ID, item.scope)
			}
			scopedName := item.scope + ":" + name
			if _, exists := seen[scopedName]; exists {
				return nil, fmt.Errorf("pipeline %q checkpoint fingerprint %q is duplicated", prepared.resolved.ID, scopedName)
			}
			seen[scopedName] = struct{}{}
			values = append(values, CheckpointFingerprint{Name: scopedName, Value: value})
		}
	}
	sort.Slice(values, func(i, j int) bool { return values[i].Name < values[j].Name })
	return values, nil
}

func appendValidatorFingerprintComponents(components []checkpointFingerprintComponent, ownerScope string, chain preparedValidatorChain) []checkpointFingerprintComponent {
	for index, validator := range chain.validators {
		scope := fmt.Sprintf("%s:validator:%d:%s", ownerScope, index, validator.resolved.Binding.Module)
		components = append(components, checkpointFingerprintComponent{scope: scope, module: preparedValidatorImplementation(validator)})
	}
	return components
}

func preparedValidatorImplementation(validator preparedValidator) any {
	switch validator.resolved.Target {
	case ValidatorTargetTyped:
		return validator.typed
	case ValidatorTargetChunk:
		return validator.chunk
	case ValidatorTargetSerialized:
		return validator.serialized
	default:
		return nil
	}
}
