// This file renders deterministic JSON and human-readable Markdown replay reports.
package evaluation

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	replayReportJSONFile = "report.json"
	replayReportMarkdown = "report.md"
)

func WriteReplayReport(outputDir string, report *ReplayReport) (string, string, error) {
	if outputDir == "" || report == nil {
		return "", "", fmt.Errorf("evaluation: replay report output and value are required")
	}
	if err := os.MkdirAll(outputDir, 0o755); err != nil {
		return "", "", fmt.Errorf("evaluation: create replay report directory: %w", err)
	}

	jsonData, err := json.MarshalIndent(report, "", "  ")
	if err != nil {
		return "", "", fmt.Errorf("evaluation: encode replay report: %w", err)
	}
	jsonPath := filepath.Join(outputDir, replayReportJSONFile)
	if err := os.WriteFile(jsonPath, append(jsonData, '\n'), 0o644); err != nil {
		return "", "", fmt.Errorf("evaluation: write replay JSON report: %w", err)
	}

	markdownPath := filepath.Join(outputDir, replayReportMarkdown)
	if err := os.WriteFile(markdownPath, []byte(RenderReplayReportMarkdown(report)), 0o644); err != nil {
		return "", "", fmt.Errorf("evaluation: write replay Markdown report: %w", err)
	}
	return jsonPath, markdownPath, nil
}

func RenderReplayReportMarkdown(report *ReplayReport) string {
	var output strings.Builder
	fmt.Fprintf(&output, "# Autoscaling Replay Report\n\n")
	fmt.Fprintf(&output, "## Bottom Line\n\n")
	if report.Summary.HPAReferencePoints > 0 {
		fmt.Fprintf(&output, "- **Stability:** Policy changed the replica target **%d times**; HPA changed it **%d times**. %s Policy reversed scaling direction **%d times**; HPA reversed **%d times**.\n",
			report.Summary.TargetChanges, report.Summary.HPATargetChanges,
			formatTargetChangeComparison(report.Summary.TargetChanges, report.Summary.HPATargetChanges),
			report.Summary.DirectionReversals, report.Summary.HPADirectionReversals)
		fmt.Fprintf(&output, "- **Capacity:** %s\n", formatCapacityComparison(report.Summary.RecommendedReplicaHours, report.Summary.HPAReplicaHours))
	} else {
		fmt.Fprintf(&output, "- **HPA comparison:** Unavailable because the historical data has no HPA replica target.\n")
	}
	fmt.Fprintf(&output, "- **Data quality:** The replay completed **%d of %d** scheduled evaluations (%.1f%%). **%d** %s skipped and **%d** returned Policy errors.\n",
		report.Summary.EvaluatedPoints, report.Summary.ScheduledPoints,
		percentage(report.Summary.EvaluatedPoints, report.Summary.ScheduledPoints),
		report.Summary.SkippedPoints, wasOrWere(report.Summary.SkippedPoints), report.Summary.PolicyErrorPoints)
	fmt.Fprintf(&output, "- **Important limitation:** This does not prove cost savings because Policy recommendations were not executed.\n\n")

	fmt.Fprintf(&output, "## What Was Replayed\n\n")
	fmt.Fprintf(&output, "| Item | Value |\n|---|---|\n")
	fmt.Fprintf(&output, "| Experiment | `%s` |\n", report.ExperimentID)
	fmt.Fprintf(&output, "| Policy | `%s` |\n", report.PolicyType)
	fmt.Fprintf(&output, "| Component | `%s` |\n", report.Dataset.Component)
	fmt.Fprintf(&output, "| Cluster | `%s` |\n", report.Dataset.Target.ClusterID)
	fmt.Fprintf(&output, "| Historical data period | `%s` to `%s` |\n", report.Dataset.Window.Start.Format(timeLayout), report.Dataset.Window.End.Format(timeLayout))
	fmt.Fprintf(&output, "| Historical metrics sampled every | %s |\n", formatDuration(report.Dataset.Window.StepSeconds))
	fmt.Fprintf(&output, "| Policy called every | %s |\n\n", formatDuration(report.EvaluationIntervalSeconds))
	fmt.Fprintf(&output, "Policy is the candidate being tested. HPA is the historical production baseline.\n\n")
	fmt.Fprintf(&output, "One evaluation means one Policy call at one scheduled time. The call sees only historical metrics at or before that time.\n\n")

	fmt.Fprintf(&output, "## Was the Data Usable?\n\n")
	fmt.Fprintf(&output, "| Result | Count |\n|---|---:|\n")
	fmt.Fprintf(&output, "| Planned Policy calls | %d |\n", report.Summary.ScheduledPoints)
	fmt.Fprintf(&output, "| Completed Policy calls | %d |\n", report.Summary.EvaluatedPoints)
	fmt.Fprintf(&output, "| Skipped because required metrics were stale or inconsistent | %d |\n", report.Summary.SkippedPoints)
	fmt.Fprintf(&output, "| Policy errors | %d |\n", report.Summary.PolicyErrorPoints)
	fmt.Fprintf(&output, "| Completed calls that also had an HPA target | %s |\n\n", formatCountRate(report.Summary.HPAReferencePoints, report.Summary.EvaluatedPoints))
	fmt.Fprintf(&output, "A skipped call is not a Policy decision. The replay skips it instead of guessing missing or inconsistent input.\n\n")

	fmt.Fprintf(&output, "## Did the Policy Stay Stable?\n\n")
	fmt.Fprintf(&output, "| Measure | Policy | HPA |\n|---|---:|---:|\n")
	fmt.Fprintf(&output, "| Replica target changes | %d | %s |\n", report.Summary.TargetChanges, formatOptionalInt(report.Summary.HPATargetChanges, report.Summary.HPAReferencePoints))
	fmt.Fprintf(&output, "| Direction reversals | %d | %s |\n", report.Summary.DirectionReversals, formatOptionalInt(report.Summary.HPADirectionReversals, report.Summary.HPAReferencePoints))
	fmt.Fprintf(&output, "| Reversals per target change | %s | %s |\n\n", formatReversalRate(report.Summary.DirectionReversals, report.Summary.TargetChanges), formatReversalRate(report.Summary.HPADirectionReversals, report.Summary.HPATargetChanges))
	fmt.Fprintf(&output, "Fewer target changes and direction reversals are better. A direction reversal means scaling out after scaling in, or scaling in after scaling out. Frequent reversals indicate churn.\n\n")

	fmt.Fprintf(&output, "## How Much Capacity Did the Policy Request?\n\n")
	fmt.Fprintf(&output, "| Capacity over time | Replica-hours |\n|---|---:|\n")
	fmt.Fprintf(&output, "| Replicas that historically ran | %.3f |\n", report.Summary.ObservedReplicaHours)
	fmt.Fprintf(&output, "| HPA target | %s |\n", formatOptionalFloat(report.Summary.HPAReplicaHours, report.Summary.ComparableReplicaHourIntervals))
	fmt.Fprintf(&output, "| Policy target | %.3f |\n\n", report.Summary.RecommendedReplicaHours)
	fmt.Fprintf(&output, "Replica-hours combine replica count and time. Two replicas for three hours equal six replica-hours. The table uses %s time intervals where historical replicas, HPA targets, and Policy targets were all available.\n\n",
		formatCountRate(report.Summary.ComparableReplicaHourIntervals, report.Summary.ScheduledReplicaHourIntervals))
	fmt.Fprintf(&output, "The Policy targets were recorded but not applied. This is a capacity comparison, not measured cost savings.\n\n")

	episodesShown := len(report.Episodes)
	if episodesShown > maxMarkdownEpisodes {
		episodesShown = maxMarkdownEpisodes
	}
	fmt.Fprintf(&output, "## When Did the Recommendation Change?\n\n")
	fmt.Fprintf(&output, "Each row is one period during which the Policy kept the same recommendation.\n\n")
	fmt.Fprintf(&output, "| Start | End | Policy calls | Why this row starts | Tier | Replicas |\n|---|---|---:|---|---|---:|\n")
	for i := 0; i < episodesShown; i++ {
		episode := &report.Episodes[i]
		fmt.Fprintf(&output, "| %s | %s | %d | %s | `%s` | %d |\n",
			episode.StartAt.Format(timeLayout), episode.EndAt.Format(timeLayout), episode.DecisionCount,
			formatRecommendationStartReason(episode.StartReason), episode.Desired.Tier, episode.Desired.Replicas)
	}
	if len(report.Episodes) == 0 {
		fmt.Fprintf(&output, "| _No successful policy evaluations_ | - | - | - | - | - |\n")
	}
	if episodesShown < len(report.Episodes) {
		fmt.Fprintf(&output, "\nOnly the first %d of %d periods are shown. `report.json` contains every Policy call.\n", episodesShown, len(report.Episodes))
	}

	fmt.Fprintf(&output, "\n## Run Identity\n\n")
	fmt.Fprintf(&output, "| Input | Identity |\n|---|---|\n")
	fmt.Fprintf(&output, "| Policy source | `%s` |\n", report.PolicyRevision)
	fmt.Fprintf(&output, "| Experiment configuration | `%s` |\n", report.ExperimentDigest)
	fmt.Fprintf(&output, "| Historical dataset | `%s` |\n", report.Dataset.ID)
	fmt.Fprintf(&output, "| Historical archive | `%s` |\n", report.Dataset.ArchiveDigest)
	return output.String()
}

func formatCountRate(count, total int) string {
	if total == 0 {
		return "N/A"
	}
	return fmt.Sprintf("%d/%d (%.1f%%)", count, total, float64(count)*100/float64(total))
}

func percentage(count, total int) float64 {
	if total == 0 {
		return 0
	}
	return float64(count) * 100 / float64(total)
}

func wasOrWere(count int) string {
	if count == 1 {
		return "was"
	}
	return "were"
}

func formatTargetChangeComparison(policyChanges, hpaChanges int) string {
	switch {
	case policyChanges == hpaChanges:
		return "They changed it equally often."
	case hpaChanges == 0:
		return "Policy changed it while HPA did not, which means more scaling churn."
	case policyChanges > hpaChanges:
		return fmt.Sprintf("Policy changed it **%.1fx as often**, which means more scaling churn.", float64(policyChanges)/float64(hpaChanges))
	default:
		return fmt.Sprintf("Policy changed it **%.1f%% less often**, which means less scaling churn.", float64(hpaChanges-policyChanges)*100/float64(hpaChanges))
	}
}

func formatCapacityComparison(policyHours, hpaHours float64) string {
	switch {
	case policyHours == hpaHours:
		return fmt.Sprintf("Policy and HPA both requested **%.3f replica-hours**.", policyHours)
	case hpaHours == 0:
		return fmt.Sprintf("Policy requested **%.3f replica-hours**; HPA requested **0.000 replica-hours**, so a percentage comparison is unavailable.", policyHours)
	case policyHours < hpaHours:
		return fmt.Sprintf("Policy requested **%.3f replica-hours**, **%.1f%% less** than HPA's **%.3f replica-hours**.",
			policyHours, (hpaHours-policyHours)*100/hpaHours, hpaHours)
	default:
		return fmt.Sprintf("Policy requested **%.3f replica-hours**, **%.1f%% more** than HPA's **%.3f replica-hours**.",
			policyHours, (policyHours-hpaHours)*100/hpaHours, hpaHours)
	}
}

func formatReversalRate(reversals, changes int) string {
	if changes == 0 {
		return "N/A"
	}
	return fmt.Sprintf("%.1f%%", float64(reversals)*100/float64(changes))
}

func formatRecommendationStartReason(reason RecommendationStartReason) string {
	switch reason {
	case RecommendationStartInitial:
		return "Initial recommendation"
	case RecommendationStartTargetChanged:
		return "Target changed"
	case RecommendationStartAfterSkipped:
		return "Resumed after skipped input"
	case RecommendationStartAfterPolicyError:
		return "Resumed after Policy error"
	default:
		return string(reason)
	}
}

func formatOptionalFloat(value float64, referencePoints int) string {
	if referencePoints == 0 {
		return "N/A"
	}
	return fmt.Sprintf("%.3f", value)
}

func formatOptionalInt(value, referencePoints int) string {
	if referencePoints == 0 {
		return "N/A"
	}
	return fmt.Sprintf("%d", value)
}

func formatDuration(seconds int64) string {
	if seconds == 0 {
		return "0 seconds"
	}
	if seconds%int64(time.Hour/time.Second) == 0 {
		return formatDurationUnit(seconds/int64(time.Hour/time.Second), "hour")
	}
	if seconds%int64(time.Minute/time.Second) == 0 {
		return formatDurationUnit(seconds/int64(time.Minute/time.Second), "minute")
	}
	return (time.Duration(seconds) * time.Second).String()
}

func formatDurationUnit(value int64, unit string) string {
	if value == 1 {
		return fmt.Sprintf("%d %s", value, unit)
	}
	return fmt.Sprintf("%d %ss", value, unit)
}

const (
	timeLayout          = "2006-01-02T15:04:05Z"
	maxMarkdownEpisodes = 20
)
