// Package autoprof provides automatic profiling capabilities for Go applications.
// Usage:
//
//			# List the files inside the pod's dump directory
//			kubectl exec $POD_NAME -n tidb-management-service -- ls /pprof-dumps
//
//			# Upload the profile to s3
//		  export AWS_ACCESS_KEY_ID=""
//		  export AWS_SECRET_ACCESS_KEY=""
//		  export AWS_SESSION_TOKEN=""
//			aws s3 cp <local_file_path> s3://<your_bucket_name>/<destination_key>
//
//	    # Diff two profiles.
//	    go tool pprof -http=:8081 <local_file_path1> <local_file_path2>
package autoprof

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"

	"github.com/daangn/autopprof"
	"github.com/daangn/autopprof/report"
)

const path = "/pprof-dumps"

var DefaultOption = autopprof.Option{
	CPUThreshold: 0.6,
	MemThreshold: 0.6,
	ReportAll:    true,
	Reporter:     LocalPathReporter{},
}

type LocalPathReporter struct{}

// cleanupOldFiles removes files older than 7 days from the specified directory.
func (r LocalPathReporter) cleanupOldFiles() error {
	entries, err := os.ReadDir(path)
	if err != nil {
		if os.IsNotExist(err) {
			// Directory doesn't exist yet, no cleanup needed
			return nil
		}
		return fmt.Errorf("failed to read directory %s: %w", path, err)
	}

	cutoffTime := time.Now().Add(-7 * 24 * time.Hour)
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		info, err := entry.Info()
		if err != nil {
			continue
		}

		if info.ModTime().Before(cutoffTime) {
			filePath := filepath.Join(path, entry.Name())
			if err := os.Remove(filePath); err != nil {
				// Log but don't fail on cleanup errors
				continue
			}
		}
	}
	return nil
}

// saveProfile saves the profile data to a file with the given name.
func (r LocalPathReporter) saveProfile(_ context.Context, reader io.Reader, filename string) error {
	// Validate input
	if reader == nil {
		return fmt.Errorf("reader cannot be nil")
	}
	if filename == "" {
		return fmt.Errorf("filename cannot be empty")
	}

	// Cleanup old files before saving
	if err := r.cleanupOldFiles(); err != nil {
		return err
	}

	// Ensure the directory exists
	if err := os.MkdirAll(path, 0o755); err != nil {
		return fmt.Errorf("failed to create directory %s: %w", path, err)
	}

	// Create the profile file
	filePath := filepath.Join(path, filename)
	file, err := os.Create(filePath)
	if err != nil {
		return fmt.Errorf("failed to create file %s: %w", filePath, err)
	}
	defer file.Close()

	// Copy the profile data to the file
	if _, err := io.Copy(file, reader); err != nil {
		return fmt.Errorf("failed to write profile data to %s: %w", filePath, err)
	}

	return nil
}

func (r LocalPathReporter) ReportCPUProfile(ctx context.Context, reader io.Reader, ci report.CPUInfo) error {
	timestamp := time.Now().Format("20060102_150405")
	filename := fmt.Sprintf("cpu_profile_%s_usage_%.2f.pprof", timestamp, ci.UsagePercentage)
	return r.saveProfile(ctx, reader, filename)
}

// ReportHeapProfile sends the heap profiling data to the specific destination.
func (r LocalPathReporter) ReportHeapProfile(ctx context.Context, reader io.Reader, mi report.MemInfo) error {
	timestamp := time.Now().Format("20060102_150405")
	filename := fmt.Sprintf("heap_profile_%s_usage_%.2f.pprof", timestamp, mi.UsagePercentage)
	return r.saveProfile(ctx, reader, filename)
}

// ReportGoroutineProfile sends the goroutine profiling data to the specific destination.
func (r LocalPathReporter) ReportGoroutineProfile(ctx context.Context, reader io.Reader, gi report.GoroutineInfo) error {
	timestamp := time.Now().Format("20060102_150405")
	filename := fmt.Sprintf("goroutine_profile_%s_count_%d.pprof", timestamp, gi.Count)
	return r.saveProfile(ctx, reader, filename)
}
