package config

import (
	"reflect"
	"strings"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)

func (c Config) Redacted() Config {
	return redactConfig(cloneConfig(c))
}

func (c Config) RedactedSummaryPayload() any {
	return c.Redacted()
}

func (e EffectiveConfig) RedactedSummaryPayload() any {
	return EffectiveConfig{
		Config:             e.Config.Redacted(),
		PipelineID:         e.PipelineID,
		Only:               append([]string(nil), e.Only...),
		ReferenceOverrides: append([]pipeline.ReferenceBinding(nil), e.ReferenceOverrides...),
		ReferenceUnbinds:   append([]pipeline.ReferenceUnbind(nil), e.ReferenceUnbinds...),
		ResolvedPipeline:   cloneResolvedPipeline(e.ResolvedPipeline),
	}
}

func (e EffectiveConfig) RedactedResolvedPipelinePayload() pipeline.ResolvedPipeline {
	return cloneResolvedPipeline(e.ResolvedPipeline)
}

func cloneResolvedPipeline(in pipeline.ResolvedPipeline) pipeline.ResolvedPipeline {
	out := in
	out.ConfiguredValidationPolicy = cloneValidationPolicyOverride(in.ConfiguredValidationPolicy)
	out.Input = redactBinding(cloneModuleBinding(in.Input))
	out.Chunk = redactBinding(cloneModuleBinding(in.Chunk))
	out.ChunkReferences = pipeline.CloneReferenceTarget(in.ChunkReferences)
	out.Output = redactBinding(cloneModuleBinding(in.Output))
	if len(in.ValidatorChains) > 0 {
		out.ValidatorChains = make([]pipeline.ResolvedValidatorChain, len(in.ValidatorChains))
		for i, chain := range in.ValidatorChains {
			out.ValidatorChains[i] = cloneResolvedValidatorChain(chain)
		}
	}
	if len(in.Steps) > 0 {
		out.Steps = make([]pipeline.ResolvedPipelineStep, len(in.Steps))
		for i, step := range in.Steps {
			out.Steps[i] = pipeline.ResolvedPipelineStep{ID: step.ID}
			if len(step.ArtifactLanes) > 0 {
				out.Steps[i].ArtifactLanes = make([]pipeline.ResolvedArtifactLane, len(step.ArtifactLanes))
				for j, lane := range step.ArtifactLanes {
					out.Steps[i].ArtifactLanes[j] = cloneResolvedArtifactLane(lane)
				}
			}
		}
	}
	return out
}

func cloneResolvedValidatorChain(in pipeline.ResolvedValidatorChain) pipeline.ResolvedValidatorChain {
	out := in
	if len(in.Validators) > 0 {
		out.Validators = make([]pipeline.ResolvedValidator, len(in.Validators))
		for i, validator := range in.Validators {
			out.Validators[i] = pipeline.ResolvedValidator{
				Binding:        redactBinding(cloneModuleBinding(validator.Binding)),
				ExecutionClass: validator.ExecutionClass,
				Target:         validator.Target,
				ArtifactKind:   validator.ArtifactKind,
			}
		}
	}
	return out
}

func cloneResolvedArtifactLane(in pipeline.ResolvedArtifactLane) pipeline.ResolvedArtifactLane {
	out := in
	out.Extract = redactBinding(cloneModuleBinding(in.Extract))
	out.Merge = redactBinding(cloneModuleBinding(in.Merge))
	out.Normalize = redactBinding(cloneModuleBinding(in.Normalize))
	out.ExtractReferences = pipeline.CloneReferenceTarget(in.ExtractReferences)
	out.MergeReferences = pipeline.CloneReferenceTarget(in.MergeReferences)
	out.NormalizeReferences = pipeline.CloneReferenceTarget(in.NormalizeReferences)
	if len(in.Validators) > 0 {
		out.Validators = make([]pipeline.ModuleBinding, len(in.Validators))
		for i, binding := range in.Validators {
			out.Validators[i] = redactBinding(cloneModuleBinding(binding))
		}
	}
	return out
}

func redactConfig(cfg Config) Config {
	for id, profile := range cfg.Pipelines {
		profile.Input = redactBinding(profile.Input)
		profile.Chunk = redactBinding(profile.Chunk)
		profile.Output = redactBinding(profile.Output)
		redactLanes := func(lanes map[string]pipeline.ArtifactLaneProfile) {
			for laneID, lane := range lanes {
				lane.Extract = redactBinding(lane.Extract)
				lane.Merge = redactBinding(lane.Merge)
				lane.Normalize = redactBinding(lane.Normalize)
				for i := range lane.Validators {
					lane.Validators[i] = redactBinding(lane.Validators[i])
				}
				lanes[laneID] = lane
			}
		}
		redactLanes(profile.Artifacts)
		for i := range profile.Steps {
			redactLanes(profile.Steps[i].Artifacts)
		}
		cfg.Pipelines[id] = profile
	}
	return cfg
}

func redactBinding(binding pipeline.ModuleBinding) pipeline.ModuleBinding {
	binding.Options = redactOptions(binding.Options)
	for i := range binding.Validators.Validators {
		binding.Validators.Validators[i] = redactBinding(binding.Validators.Validators[i])
	}
	return binding
}

func redactOptions(values map[string]any) map[string]any {
	if len(values) == 0 {
		return nil
	}
	out := make(map[string]any, len(values))
	for key, value := range values {
		if sensitiveConfigKey(key) {
			out[key] = "[REDACTED]"
			continue
		}
		out[key] = redactOptionValue(value)
	}
	return out
}

func redactOptionValue(value any) any {
	if value == nil {
		return nil
	}
	reflected := reflect.ValueOf(value)
	switch reflected.Kind() {
	case reflect.Map:
		if reflected.Type().Key().Kind() != reflect.String {
			return value
		}
		if reflected.IsNil() {
			return nil
		}
		out := make(map[string]any, reflected.Len())
		iterator := reflected.MapRange()
		for iterator.Next() {
			key := iterator.Key().String()
			if sensitiveConfigKey(key) {
				out[key] = "[REDACTED]"
				continue
			}
			out[key] = redactOptionValue(iterator.Value().Interface())
		}
		return out
	case reflect.Slice:
		if reflected.IsNil() {
			return nil
		}
		if reflected.Type().Elem().Kind() == reflect.Uint8 {
			out := reflect.MakeSlice(reflected.Type(), reflected.Len(), reflected.Len())
			reflect.Copy(out, reflected)
			return out.Interface()
		}
		fallthrough
	case reflect.Array:
		items := make([]any, reflected.Len())
		for i := 0; i < reflected.Len(); i++ {
			items[i] = redactOptionValue(reflected.Index(i).Interface())
		}
		return items
	default:
		return value
	}
}

func sensitiveConfigKey(key string) bool {
	key = strings.ToLower(key)
	return strings.Contains(key, "api_key") || strings.Contains(key, "apikey") || strings.Contains(key, "authorization") || strings.Contains(key, "bearer") || strings.Contains(key, "password") || strings.Contains(key, "secret") || strings.Contains(key, "token")
}
