package analyzer

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/gruntwork-io/terragrunt/test/flake/types"
)

const markdownTemplate = `# Flaky Test Analysis Report

Generated: {{.GeneratedAt.Format "2006-01-02 15:04:05 UTC"}}

## Summary

| Metric | Value |
|--------|-------|
| Total Runs Analyzed | {{.TotalRuns}} |
| Failed Runs | {{.FailedRuns}} |
| Total Test Failures | {{.TotalFailures}} |
| Unique Failing Tests | {{.UniqueTests}} |

## Top Flaky Tests

| Rank | Test Name | Failures | Rate |
|------|-----------|----------|------|
{{- range $i, $test := .TestStats}}
| {{add $i 1}} | {{$test.TestName}} | {{$test.TotalFailures}} | {{printf "%.1f" (mul $test.FailureRate 100)}}% |
{{- end}}

## Detailed Failure Analysis

{{range .TestStats}}
### {{.TestName}}

| Metric | Value |
|--------|-------|
| Total Failures | {{.TotalFailures}} |
| Failure Rate | {{printf "%.1f" (mul .FailureRate 100)}}% |
| First Seen | {{.FirstSeen.Format "2006-01-02"}} |
| Last Seen | {{.LastSeen.Format "2006-01-02"}} |

{{if .Failures}}
<details>
<summary>View {{len .Failures}} failure(s)</summary>

{{range .Failures}}
#### Run {{.RunID}} - {{.JobName}}

{{if .RunURL}}**URL:** {{.RunURL}}{{end}}

{{if .ErrorMessage}}**Error:** {{.ErrorMessage}}{{end}}

` + "```" + `
{{.LogSnippet}}
` + "```" + `

---

{{end}}
</details>
{{end}}

{{end}}

---

*Report generated by flake utility*
`

// GenerateMarkdownReport generates a markdown report from the analysis.
func GenerateMarkdownReport(report types.AnalysisReport, outputPath string) error {
	funcMap := template.FuncMap{
		"add": func(a, b int) int { return a + b },
		"mul": func(a, b float64) float64 { return a * b },
	}

	tmpl, err := template.New("report").Funcs(funcMap).Parse(markdownTemplate)
	if err != nil {
		return fmt.Errorf("failed to parse template: %w", err)
	}

	// Ensure directory exists
	if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	file, err := os.Create(outputPath)
	if err != nil {
		return fmt.Errorf("failed to create file: %w", err)
	}
	defer file.Close()

	// Set GeneratedAt if not set
	if report.GeneratedAt.IsZero() {
		report.GeneratedAt = time.Now().UTC()
	}

	if err := tmpl.Execute(file, report); err != nil {
		return fmt.Errorf("failed to execute template: %w", err)
	}

	return nil
}

// GenerateJSONReport generates a JSON report from the analysis.
func GenerateJSONReport(report types.AnalysisReport, outputPath string) error {
	// Ensure directory exists
	if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Set GeneratedAt if not set
	if report.GeneratedAt.IsZero() {
		report.GeneratedAt = time.Now().UTC()
	}

	data, err := json.MarshalIndent(report, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal JSON: %w", err)
	}

	if err := os.WriteFile(outputPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}

	return nil
}

// GenerateTestRankingsReport generates a simple test rankings report.
func GenerateTestRankingsReport(stats []types.TestStats, outputPath string) error {
	// Ensure directory exists
	if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	var buf bytes.Buffer
	buf.WriteString("# Flaky Test Rankings\n\n")
	fmt.Fprintf(&buf, "Generated: %s\n\n", time.Now().UTC().Format("2006-01-02 15:04:05 UTC"))
	buf.WriteString("Tests ranked by failure count:\n\n")

	for i, s := range stats {
		fmt.Fprintf(&buf, "%d. **%s** - %d failures (%.1f%%)\n",
			i+1, s.TestName, s.TotalFailures, s.FailureRate*100)
	}

	if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}

	return nil
}

// GenerateJobSummaryReport generates a report grouped by job.
func GenerateJobSummaryReport(failures []types.TestFailure, outputPath string) error {
	// Ensure directory exists
	if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	byJob := GroupFailuresByJob(failures)

	var buf bytes.Buffer
	buf.WriteString("# Failures by Job\n\n")
	fmt.Fprintf(&buf, "Generated: %s\n\n", time.Now().UTC().Format("2006-01-02 15:04:05 UTC"))

	// Get sorted job names
	var jobNames []string
	for name := range byJob {
		jobNames = append(jobNames, name)
	}

	// Sort by failure count
	type jobFailures struct {
		name     string
		failures []types.TestFailure
	}
	var sorted []jobFailures
	for _, name := range jobNames {
		sorted = append(sorted, jobFailures{name, byJob[name]})
	}

	// Sort by number of failures
	for i := 0; i < len(sorted); i++ {
		for j := i + 1; j < len(sorted); j++ {
			if len(sorted[j].failures) > len(sorted[i].failures) {
				sorted[i], sorted[j] = sorted[j], sorted[i]
			}
		}
	}

	for _, jf := range sorted {
		fmt.Fprintf(&buf, "## %s\n\n", jf.name)
		fmt.Fprintf(&buf, "**Total failures:** %d\n\n", len(jf.failures))

		// Count unique tests
		testCounts := make(map[string]int)
		for _, f := range jf.failures {
			testCounts[f.TestName]++
		}

		buf.WriteString("| Test | Failures |\n")
		buf.WriteString("|------|----------|\n")
		for test, count := range testCounts {
			fmt.Fprintf(&buf, "| %s | %d |\n", test, count)
		}
		buf.WriteString("\n")
	}

	if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}

	return nil
}

// TruncateSnippet truncates a log snippet to a maximum length.
func TruncateSnippet(snippet string, maxLines int) string {
	lines := strings.Split(snippet, "\n")
	if len(lines) <= maxLines {
		return snippet
	}

	// Keep first and last portions
	half := maxLines / 2
	result := append(lines[:half], "... [truncated] ...")
	result = append(result, lines[len(lines)-half:]...)
	return strings.Join(result, "\n")
}
