package cli

import (
	"errors"
	"fmt"
	"io"

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

type DebugTerminalWriter interface {
	WriteRunReport(debugbundle.RunReport) error
	WriteError(string) error
}

type pipelineCommandState struct {
	report       debugbundle.RunReport
	terminalized bool
}

func newPipelineCommandState(runID, pipelineID, outputPath string) *pipelineCommandState {
	return &pipelineCommandState{report: debugbundle.RunReport{
		RunID:      runID,
		PipelineID: pipelineID,
		OutputPath: outputPath,
	}}
}

func (s *pipelineCommandState) setDebugPath(debugPath string) {
	if s != nil {
		s.report.DebugPath = debugPath
	}
}

func (s *pipelineCommandState) observeOutput(output pipeline.RunOutput, diagnostics contracts.DiagnosticProjection) {
	if s == nil {
		return
	}
	s.report.OutputCount = len(output.NormalizeOutputs)
	s.report.RejectedCount = len(output.Rejected)
	s.report.WarningGroupCount = len(diagnostics.Warnings)
	s.report.WarningOccurrenceCount = diagnostics.WarningOccurrenceCount
	s.report.DiagnosticGroupCount = len(diagnostics.Diagnostics)
	s.report.DiagnosticOccurrenceCount = diagnostics.DiagnosticOccurrenceCount
	s.report.DiagnosticsTruncated = output.Diagnostics.Truncated
	s.report.ValidationStatus = output.Manifest.ValidationStatus
}

func (s *pipelineCommandState) terminalize(writer DebugTerminalWriter, primaryErr error) (error, error) {
	if s == nil || s.terminalized {
		return primaryErr, nil
	}
	s.terminalized = true
	if writer == nil {
		return primaryErr, nil
	}

	report := s.report
	report.Succeeded = primaryErr == nil
	reportErr := writer.WriteRunReport(report)
	if reportErr != nil {
		reportErr = fmt.Errorf("write debug run report: %w", reportErr)
		if primaryErr == nil {
			primaryErr = reportErr
			reportErr = nil
		}
	}

	var errorLogErr error
	if primaryErr != nil {
		if err := writer.WriteError(primaryErr.Error()); err != nil {
			errorLogErr = fmt.Errorf("write debug error log: %w", err)
		}
	}
	return primaryErr, errors.Join(reportErr, errorLogErr)
}

func failPipelineCommand(stderr io.Writer, state *pipelineCommandState, writer DebugTerminalWriter, primaryErr error, persistenceErrs ...error) int {
	primaryErr, terminalErr := state.terminalize(writer, primaryErr)
	persistenceErrs = append(persistenceErrs, terminalErr)
	return writePipelineCommandFailure(stderr, state, primaryErr, errors.Join(persistenceErrs...))
}

func writePipelineCommandFailure(stderr io.Writer, state *pipelineCommandState, primaryErr, persistenceErr error) int {
	fmt.Fprintf(stderr, "notarius: %v\n", primaryErr)
	if persistenceErr != nil {
		fmt.Fprintf(stderr, "notarius: %v\n", persistenceErr)
	}
	if state != nil && state.report.DebugPath != "" {
		fmt.Fprintf(stderr, "notarius: debug=%s\n", state.report.DebugPath)
	}
	return 1
}
