package healthcheck

import (
	"testing"
	"time"

	"github.com/onsi/gomega"
)

func TestCalculateNextRunTime(t *testing.T) {
	g := gomega.NewWithT(t)

	tests := []struct {
		name       string
		expr       string
		shouldWork bool
	}{
		{
			name:       "standard hourly cron",
			expr:       "0 * * * *",
			shouldWork: true,
		},
		{
			name:       "every 5 minutes",
			expr:       "*/5 * * * *",
			shouldWork: true,
		},
		{
			name:       "daily at midnight",
			expr:       "0 0 * * *",
			shouldWork: true,
		},
		{
			name:       "weekly on Sunday",
			expr:       "0 0 * * 0",
			shouldWork: true,
		},
		{
			name:       "monthly on 1st",
			expr:       "0 0 1 * *",
			shouldWork: true,
		},
		{
			name:       "at specific time",
			expr:       "30 14 * * *",
			shouldWork: true,
		},
		{
			name:       "@hourly descriptor",
			expr:       "@hourly",
			shouldWork: true,
		},
		{
			name:       "@daily descriptor",
			expr:       "@daily",
			shouldWork: true,
		},
		{
			name:       "@weekly descriptor",
			expr:       "@weekly",
			shouldWork: true,
		},
		{
			name:       "@monthly descriptor",
			expr:       "@monthly",
			shouldWork: true,
		},
		{
			name:       "@every duration",
			expr:       "@every 1h30m",
			shouldWork: true,
		},
		{
			name:       "empty expression",
			expr:       "",
			shouldWork: false,
		},
		{
			name:       "invalid expression",
			expr:       "invalid",
			shouldWork: false,
		},
		{
			name:       "6-field expression (not supported)",
			expr:       "0 0 * * * *",
			shouldWork: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			now := time.Now()
			next := calculateNextRunTime(tt.expr, now)

			if tt.shouldWork {
				g.Expect(next).NotTo(gomega.BeNil())
				g.Expect(*next).To(gomega.BeTemporally(">", now))
			} else {
				g.Expect(next).To(gomega.BeNil())
			}
		})
	}
}

func TestValidateCronExpression(t *testing.T) {
	g := gomega.NewWithT(t)

	tests := []struct {
		name    string
		expr    string
		wantErr bool
	}{
		{
			name:    "valid cron expression",
			expr:    "0 * * * *",
			wantErr: false,
		},
		{
			name:    "valid descriptor",
			expr:    "@hourly",
			wantErr: false,
		},
		{
			name:    "empty expression",
			expr:    "",
			wantErr: false, // empty is considered valid (no-op)
		},
		{
			name:    "invalid expression",
			expr:    "bad expression",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateCronExpression(tt.expr)
			if tt.wantErr {
				g.Expect(err).To(gomega.HaveOccurred())
			} else {
				g.Expect(err).To(gomega.Succeed())
			}
		})
	}
}

func TestCalculateNextRunTimeWithBaseTime(t *testing.T) {
	g := gomega.NewWithT(t)

	// Test with a specific base time
	baseTime := time.Date(2024, 1, 1, 12, 30, 0, 0, time.UTC) // Jan 1, 2024 12:30:00

	// "0 * * * *" means at minute 0 of every hour
	// Next from 12:30 should be 13:00
	next := calculateNextRunTime("0 * * * *", baseTime)
	g.Expect(next).NotTo(gomega.BeNil())

	expected := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC)
	g.Expect(*next).To(gomega.BeTemporally("==", expected))
}

func TestCalculateNextRunTimeDaily(t *testing.T) {
	g := gomega.NewWithT(t)

	// Test daily at 2:30 AM
	baseTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
	next := calculateNextRunTime("30 2 * * *", baseTime)

	g.Expect(next).NotTo(gomega.BeNil())
	// Should be next day at 2:30 AM
	expected := time.Date(2024, 1, 2, 2, 30, 0, 0, time.UTC)
	g.Expect(*next).To(gomega.BeTemporally("==", expected))
}

func TestCalculateNextRunTimeWeekly(t *testing.T) {
	g := gomega.NewWithT(t)

	// Test weekly on Sunday at midnight
	// Jan 1, 2024 is Monday, so next Sunday is Jan 7
	baseTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	next := calculateNextRunTime("0 0 * * 0", baseTime)

	g.Expect(next).NotTo(gomega.BeNil())
	expected := time.Date(2024, 1, 7, 0, 0, 0, 0, time.UTC)
	g.Expect(*next).To(gomega.BeTemporally("==", expected))
}
