package cli

import (
	"encoding/json"
	"fmt"
	"io"
	"path/filepath"
	"strings"

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

const runResultSchemaVersion = "notarius.run-result.v1"

type runResult struct {
	SchemaVersion         string `json:"schema_version"`
	RunID                 string `json:"run_id"`
	PipelineID            string `json:"pipeline_id"`
	OutputDirectory       string `json:"output_directory"`
	IndexFile             string `json:"index_file,omitempty"`
	NormalizedOutputCount int    `json:"normalized_output_count"`
	RejectedOutputCount   int    `json:"rejected_output_count"`
	WarningCount          int    `json:"warning_count"`
	ValidationStatus      string `json:"validation_status"`
	DebugDirectory        string `json:"debug_directory,omitempty"`
}

func newRunResult(resolved pipeline.ResolvedPipeline, output pipeline.RunOutput, outputDirectory, debugDirectory string) (runResult, error) {
	if strings.TrimSpace(output.Manifest.RunID) == "" {
		return runResult{}, fmt.Errorf("run result requires a run ID")
	}
	if strings.TrimSpace(resolved.ID) == "" {
		return runResult{}, fmt.Errorf("run result requires a resolved pipeline ID")
	}
	if strings.TrimSpace(output.Manifest.PipelineID) == "" {
		return runResult{}, fmt.Errorf("run result requires a manifest pipeline ID")
	}
	if output.Manifest.PipelineID != resolved.ID {
		return runResult{}, fmt.Errorf("run result pipeline ID does not match resolved pipeline")
	}
	if strings.TrimSpace(output.Manifest.ValidationStatus) == "" {
		return runResult{}, fmt.Errorf("run result requires a validation status")
	}
	if strings.TrimSpace(outputDirectory) == "" {
		return runResult{}, fmt.Errorf("run result requires an output directory")
	}

	absOutputDirectory, err := filepath.Abs(outputDirectory)
	if err != nil {
		return runResult{}, fmt.Errorf("make output directory absolute: %w", err)
	}

	result := runResult{
		SchemaVersion:         runResultSchemaVersion,
		RunID:                 output.Manifest.RunID,
		PipelineID:            output.Manifest.PipelineID,
		OutputDirectory:       absOutputDirectory,
		NormalizedOutputCount: len(output.NormalizeOutputs),
		RejectedOutputCount:   len(output.Rejected),
		WarningCount:          len(output.Warnings),
		ValidationStatus:      output.Manifest.ValidationStatus,
	}

	if strings.TrimSpace(debugDirectory) != "" {
		absDebugDirectory, err := filepath.Abs(debugDirectory)
		if err != nil {
			return runResult{}, fmt.Errorf("make debug directory absolute: %w", err)
		}
		result.DebugDirectory = absDebugDirectory
	}

	if resolved.Output.Module == pipeline.DefaultOutputModule {
		indexCount := 0
		for _, file := range output.OutputFiles {
			if file.Name == "index.json" {
				indexCount++
			}
		}
		if indexCount != 1 {
			return runResult{}, fmt.Errorf("production JSON output must contain exactly one index.json file")
		}
		result.IndexFile = "index.json"
	}

	return result, nil
}

func encodeRunResult(result runResult) ([]byte, error) {
	encoded, err := json.Marshal(result)
	if err != nil {
		return nil, fmt.Errorf("encode run result: %w", err)
	}
	return append(encoded, '\n'), nil
}

func writeRunResult(writer io.Writer, content []byte) error {
	for len(content) > 0 {
		written, err := writer.Write(content)
		if written < 0 || written > len(content) {
			return io.ErrShortWrite
		}
		content = content[written:]
		if err != nil {
			return err
		}
		if written == 0 {
			return io.ErrShortWrite
		}
	}
	return nil
}
