package pipeline

import (
	"fmt"

	"gitea.maximumdirect.net/eric/notarius/internal/core/source"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	frameworkdiagnostics "gitea.maximumdirect.net/eric/notarius/internal/framework/diagnostics"
)

func terminalDiagnosticGroups(terminal producerAttemptTerminal, origin contracts.DiagnosticOrigin, chunk *source.Chunk) ([]contracts.DiagnosticGroup, error) {
	return promoteCheckpointDiagnostics(terminalCheckpointDiagnostics(terminal), origin, chunk)
}

func terminalCheckpointDiagnostics(terminal producerAttemptTerminal) []CheckpointDiagnostic {
	diagnostics := make([]CheckpointDiagnostic, 0, len(terminal.Diagnostics))
	for _, diagnostic := range terminal.Diagnostics {
		diagnostics = append(diagnostics, CheckpointDiagnostic{Diagnostic: diagnostic})
	}
	for _, record := range terminal.Validation.Diagnostics() {
		diagnostics = append(diagnostics, CheckpointDiagnostic{Diagnostic: record.diagnostic, ValidatorKey: record.validatorName})
	}
	if terminal.Action == producerTerminalIncompleteAccepted {
		for _, record := range incompleteValidationDiagnostics(terminal.Validation) {
			diagnostics = append(diagnostics, CheckpointDiagnostic{Diagnostic: record.diagnostic, ValidatorKey: record.validatorName})
		}
	}
	return cloneCheckpointDiagnostics(diagnostics)
}

func promoteCheckpointDiagnostics(diagnostics []CheckpointDiagnostic, origin contracts.DiagnosticOrigin, chunk *source.Chunk) ([]contracts.DiagnosticGroup, error) {
	groups := make([]contracts.DiagnosticGroup, 0, len(diagnostics))
	for _, record := range diagnostics {
		validatorOrigin := origin
		validatorOrigin.ValidatorKey = record.ValidatorKey
		promoted, err := promoteProducerDiagnostics([]contracts.ProducerDiagnostic{record.Diagnostic}, validatorOrigin, chunk)
		if err != nil {
			return nil, fmt.Errorf("checkpoint diagnostic: %w", err)
		}
		groups = append(groups, promoted...)
	}
	return groups, nil
}

func cloneCheckpointDiagnostics(diagnostics []CheckpointDiagnostic) []CheckpointDiagnostic {
	if len(diagnostics) == 0 {
		return nil
	}
	cloned := make([]CheckpointDiagnostic, len(diagnostics))
	for index, diagnostic := range diagnostics {
		cloned[index] = CheckpointDiagnostic{
			Diagnostic:   contracts.CloneProducerDiagnostics([]contracts.ProducerDiagnostic{diagnostic.Diagnostic})[0],
			ValidatorKey: diagnostic.ValidatorKey,
		}
	}
	return cloned
}

func promoteProducerDiagnostics(diagnostics []contracts.ProducerDiagnostic, origin contracts.DiagnosticOrigin, chunk *source.Chunk) ([]contracts.DiagnosticGroup, error) {
	if err := contracts.ValidateProducerDiagnostics(diagnostics); err != nil {
		return nil, err
	}
	if len(diagnostics) == 0 {
		return nil, nil
	}
	groups := make([]contracts.DiagnosticGroup, len(diagnostics))
	for index, diagnostic := range diagnostics {
		group := contracts.DiagnosticGroup{
			Disposition:        diagnostic.Disposition,
			Category:           diagnostic.Category,
			ReasonCode:         diagnostic.ReasonCode,
			Origin:             origin,
			OccurrenceCount:    diagnostic.OccurrenceCount,
			Samples:            cloneDiagnosticSamples(diagnostic.Samples, chunk),
			OmittedSampleCount: diagnostic.OmittedSampleCount,
		}
		if err := group.Validate(); err != nil {
			return nil, err
		}
		groups[index] = group
	}
	return groups, nil
}

func cloneDiagnosticSamples(samples []contracts.DiagnosticSample, chunk *source.Chunk) []contracts.DiagnosticSample {
	if len(samples) == 0 {
		return nil
	}
	cloned := make([]contracts.DiagnosticSample, len(samples))
	for index, sample := range samples {
		if chunk != nil {
			chunkIndex := chunk.Index
			sample.ChunkID = chunk.ID
			sample.ChunkIndex = &chunkIndex
		} else if sample.ChunkIndex != nil {
			chunkIndex := *sample.ChunkIndex
			sample.ChunkIndex = &chunkIndex
		}
		cloned[index] = sample
	}
	return cloned
}

func appendDiagnosticGroups(output *RunOutput, groups []contracts.DiagnosticGroup) {
	if output == nil || len(groups) == 0 {
		return
	}
	cloned := contracts.CloneDiagnosticCollection(contracts.DiagnosticCollection{Groups: groups}).Groups
	output.diagnosticGroups = append(output.diagnosticGroups, cloned...)
}

func finalizeDiagnostics(output *RunOutput) error {
	if output == nil {
		return nil
	}
	aggregator := frameworkdiagnostics.Aggregator{}
	for _, group := range output.diagnosticGroups {
		if err := aggregator.Add(group); err != nil {
			return err
		}
	}
	output.Diagnostics = aggregator.Collection()
	return nil
}
