package debugbundle

import (
	"fmt"
	"strings"
	"time"

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

const (
	ArtifactInvocationMetadata = "invocation.json"
	ArtifactEffectiveConfig    = "effective-config.json"
	ArtifactResolvedPipeline   = "resolved-pipeline.json"
	ArtifactResolvedReferences = "resolved-references.json"
	ArtifactCheckpointEvents   = "checkpoint-events.json"
	ArtifactRunManifest        = "run-manifest.json"
	ArtifactChunkPlan          = "chunk-plan.json"
	ArtifactRunReport          = "run-report.json"
	ArtifactDiagnostics        = "final-diagnostics.json"
	ArtifactErrorLog           = "error.log"
)

type RedactedSummaryPayload interface{ RedactedSummaryPayload() any }
type RedactedResolvedPipelinePayload interface {
	RedactedResolvedPipelinePayload() pipeline.ResolvedPipeline
}
type Invocation struct {
	Operation               string    `json:"operation"`
	PipelineID              string    `json:"pipeline_id,omitempty"`
	PipelineDigest          string    `json:"pipeline_digest,omitempty"`
	Resume                  bool      `json:"resume,omitempty"`
	RecomputeStep           string    `json:"recompute_step,omitempty"`
	InputPath               string    `json:"input_path,omitempty"`
	ConfigPath              string    `json:"config_path,omitempty"`
	ConfigSource            string    `json:"config_source,omitempty"`
	OnlyLanes               []string  `json:"only_lanes,omitempty"`
	ChunkCacheOverride      string    `json:"chunk_cache_override,omitempty"`
	ReasoningEffortOverride *string   `json:"reasoning_effort_override,omitempty"`
	SessionID               string    `json:"session_id,omitempty"`
	RunID                   string    `json:"run_id"`
	StartedAt               time.Time `json:"started_at"`
}
type RunReport struct {
	RunID                     string `json:"run_id"`
	PipelineID                string `json:"pipeline_id"`
	OutputPath                string `json:"output_path,omitempty"`
	DebugPath                 string `json:"debug_path,omitempty"`
	Succeeded                 bool   `json:"succeeded"`
	OutputCount               int    `json:"output_count"`
	RejectedCount             int    `json:"rejected_count"`
	WarningGroupCount         int    `json:"warning_group_count"`
	WarningOccurrenceCount    int    `json:"warning_occurrence_count"`
	DiagnosticGroupCount      int    `json:"diagnostic_group_count"`
	DiagnosticOccurrenceCount int    `json:"diagnostic_occurrence_count"`
	DiagnosticsTruncated      bool   `json:"diagnostics_truncated"`
	ValidationStatus          string `json:"validation_status,omitempty"`
}
type SummaryWriter struct {
	root, runID string
	createdAt   time.Time
}

func (w *SummaryWriter) WriteInvocation(payload Invocation) error {
	if w == nil {
		return fmt.Errorf("debug summary writer must not be nil")
	}
	if payload.RunID == "" {
		payload.RunID = w.runID
	}
	if payload.StartedAt.IsZero() {
		payload.StartedAt = w.createdAt
	}
	if payload.ReasoningEffortOverride != nil {
		value := *payload.ReasoningEffortOverride
		payload.ReasoningEffortOverride = &value
	}
	return w.writeJSON(ArtifactInvocationMetadata, payload)
}
func (w *SummaryWriter) WriteRedactedEffectiveConfig(payload RedactedSummaryPayload) error {
	if payload == nil {
		return fmt.Errorf("redacted summary payload must not be nil")
	}
	return w.writeJSON(ArtifactEffectiveConfig, payload.RedactedSummaryPayload())
}
func (w *SummaryWriter) WriteResolvedPipeline(payload RedactedResolvedPipelinePayload) error {
	if payload == nil {
		return fmt.Errorf("redacted resolved pipeline payload must not be nil")
	}
	return w.writeJSON(ArtifactResolvedPipeline, payload.RedactedResolvedPipelinePayload())
}
func (w *SummaryWriter) WriteResolvedReferences(v []artifacts.ReferenceProvenance) error {
	return w.writeJSON(ArtifactResolvedReferences, v)
}
func (w *SummaryWriter) WriteCheckpointEvents(v []pipeline.CheckpointEvent) error {
	return w.writeJSON(ArtifactCheckpointEvents, v)
}
func (w *SummaryWriter) WriteRunManifest(v artifacts.RunManifest) error {
	return w.writeJSON(ArtifactRunManifest, v)
}
func (w *SummaryWriter) WriteChunkPlan(v artifacts.ChunkPlanSummary) error {
	return w.writeJSON(ArtifactChunkPlan, v)
}
func (w *SummaryWriter) WriteRunReport(v RunReport) error { return w.writeJSON(ArtifactRunReport, v) }
func (w *SummaryWriter) WriteDiagnostics(v contracts.DiagnosticCollection) error {
	return w.writeJSON(ArtifactDiagnostics, contracts.CloneDiagnosticCollection(v))
}
func (w *SummaryWriter) WriteError(message string) error {
	return w.writeBytes(ArtifactErrorLog, []byte(message+"\n"))
}
func (w *SummaryWriter) writeJSON(name string, v any) error {
	if w == nil {
		return fmt.Errorf("debug summary writer must not be nil")
	}
	if err := fileio.WriteJSON(w.root, summaryName(name), v, 0o700, 0o600); err != nil {
		return fmt.Errorf("write debug summary artifact %q: %w", name, err)
	}
	return nil
}
func (w *SummaryWriter) writeBytes(name string, v []byte) error {
	if w == nil {
		return fmt.Errorf("debug summary writer must not be nil")
	}
	if err := fileio.WriteBytes(w.root, summaryName(name), v, 0o700, 0o600); err != nil {
		return fmt.Errorf("write debug summary artifact %q: %w", name, err)
	}
	return nil
}
func summaryName(name string) string {
	name = strings.TrimSpace(name)
	if name == "" || strings.ContainsAny(name, "/\\") {
		return "../invalid"
	}
	return name
}
