package configs

import (
	"testing"
	"time"
)

func TestGenerateHours(t *testing.T) {
	tests := []struct {
		name      string
		start     int64
		end       int64
		wantHours []string
	}{
		{
			name:      "same hour",
			start:     mustParseTime(t, "2025-01-15T10:30:00Z").Unix(),
			end:       mustParseTime(t, "2025-01-15T10:45:00Z").Unix(),
			wantHours: []string{"2025011510"},
		},
		{
			name:      "two consecutive hours",
			start:     mustParseTime(t, "2025-01-15T10:00:00Z").Unix(),
			end:       mustParseTime(t, "2025-01-15T11:00:00Z").Unix(),
			wantHours: []string{"2025011510", "2025011511"},
		},
		{
			name:      "cross day boundary",
			start:     mustParseTime(t, "2025-01-15T22:00:00Z").Unix(),
			end:       mustParseTime(t, "2025-01-16T02:00:00Z").Unix(),
			wantHours: []string{"2025011522", "2025011523", "2025011600", "2025011601", "2025011602"},
		},
		{
			name:      "start truncated to hour",
			start:     mustParseTime(t, "2025-03-01T05:59:59Z").Unix(),
			end:       mustParseTime(t, "2025-03-01T06:00:00Z").Unix(),
			wantHours: []string{"2025030105", "2025030106"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := generateHours(tt.start, tt.end)
			if len(got) != len(tt.wantHours) {
				t.Fatalf("generateHours() = %v, want %v", got, tt.wantHours)
			}
			for i := range got {
				if got[i] != tt.wantHours[i] {
					t.Fatalf("generateHours()[%d] = %q, want %q", i, got[i], tt.wantHours[i])
				}
			}
		})
	}
}

func TestGenerateDates(t *testing.T) {
	tests := []struct {
		name      string
		start     int64
		end       int64
		wantDates []string
	}{
		{
			name:      "same day",
			start:     mustParseTime(t, "2025-01-15T10:30:00Z").Unix(),
			end:       mustParseTime(t, "2025-01-15T23:59:00Z").Unix(),
			wantDates: []string{"20250115"},
		},
		{
			name:      "two consecutive days",
			start:     mustParseTime(t, "2025-01-15T00:00:00Z").Unix(),
			end:       mustParseTime(t, "2025-01-16T00:00:00Z").Unix(),
			wantDates: []string{"20250115", "20250116"},
		},
		{
			name:      "cross month boundary",
			start:     mustParseTime(t, "2025-01-30T00:00:00Z").Unix(),
			end:       mustParseTime(t, "2025-02-02T00:00:00Z").Unix(),
			wantDates: []string{"20250130", "20250131", "20250201", "20250202"},
		},
		{
			name:      "cross year boundary",
			start:     mustParseTime(t, "2024-12-30T00:00:00Z").Unix(),
			end:       mustParseTime(t, "2025-01-02T00:00:00Z").Unix(),
			wantDates: []string{"20241230", "20241231", "20250101", "20250102"},
		},
		{
			name:      "start truncated to day",
			start:     mustParseTime(t, "2025-03-05T23:59:59Z").Unix(),
			end:       mustParseTime(t, "2025-03-07T00:00:00Z").Unix(),
			wantDates: []string{"20250305", "20250306", "20250307"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := generateDates(tt.start, tt.end)
			if len(got) != len(tt.wantDates) {
				t.Fatalf("generateDates() = %v, want %v", got, tt.wantDates)
			}
			for i := range got {
				if got[i] != tt.wantDates[i] {
					t.Fatalf("generateDates()[%d] = %q, want %q", i, got[i], tt.wantDates[i])
				}
			}
		})
	}
}

func TestGenerateLogShards(t *testing.T) {
	tests := []struct {
		name       string
		hours      []string
		components []string
		clusterID  string
		destPrefix string
		want       []FileShard
	}{
		{
			name:       "single hour single component",
			hours:      []string{"2025011510"},
			components: []string{"tidb"},
			clusterID:  "c123",
			destPrefix: "s3://bucket/logs",
			want: []FileShard{
				{
					Index:        0,
					SourcePrefix: "diagnosis/data/c123/merged-logs/2025011510/tidb",
					DestPrefix:   "s3://bucket/logs/c123/2025011510/tidb",
					Hour:         "2025011510",
					Component:    "tidb",
					ClusterID:    "c123",
				},
			},
		},
		{
			name:       "single hour multiple components",
			hours:      []string{"2025011510"},
			components: []string{"tidb", "tikv", "pd"},
			clusterID:  "c456",
			destPrefix: "s3://bucket/logs",
			want: []FileShard{
				{
					Index:        0,
					SourcePrefix: "diagnosis/data/c456/merged-logs/2025011510/tidb",
					DestPrefix:   "s3://bucket/logs/c456/2025011510/tidb",
					Hour:         "2025011510",
					Component:    "tidb",
					ClusterID:    "c456",
				},
				{
					Index:        1,
					SourcePrefix: "diagnosis/data/c456/merged-logs/2025011510/tikv",
					DestPrefix:   "s3://bucket/logs/c456/2025011510/tikv",
					Hour:         "2025011510",
					Component:    "tikv",
					ClusterID:    "c456",
				},
				{
					Index:        2,
					SourcePrefix: "diagnosis/data/c456/merged-logs/2025011510/pd",
					DestPrefix:   "s3://bucket/logs/c456/2025011510/pd",
					Hour:         "2025011510",
					Component:    "pd",
					ClusterID:    "c456",
				},
			},
		},
		{
			name:       "multiple hours multiple components",
			hours:      []string{"2025011510", "2025011511"},
			components: []string{"tidb", "tikv"},
			clusterID:  "c789",
			destPrefix: "s3://bucket/logs",
			want: []FileShard{
				{
					Index:        0,
					SourcePrefix: "diagnosis/data/c789/merged-logs/2025011510/tidb",
					DestPrefix:   "s3://bucket/logs/c789/2025011510/tidb",
					Hour:         "2025011510",
					Component:    "tidb",
					ClusterID:    "c789",
				},
				{
					Index:        1,
					SourcePrefix: "diagnosis/data/c789/merged-logs/2025011510/tikv",
					DestPrefix:   "s3://bucket/logs/c789/2025011510/tikv",
					Hour:         "2025011510",
					Component:    "tikv",
					ClusterID:    "c789",
				},
				{
					Index:        2,
					SourcePrefix: "diagnosis/data/c789/merged-logs/2025011511/tidb",
					DestPrefix:   "s3://bucket/logs/c789/2025011511/tidb",
					Hour:         "2025011511",
					Component:    "tidb",
					ClusterID:    "c789",
				},
				{
					Index:        3,
					SourcePrefix: "diagnosis/data/c789/merged-logs/2025011511/tikv",
					DestPrefix:   "s3://bucket/logs/c789/2025011511/tikv",
					Hour:         "2025011511",
					Component:    "tikv",
					ClusterID:    "c789",
				},
			},
		},
		{
			name:       "empty inputs",
			hours:      nil,
			components: nil,
			clusterID:  "c000",
			destPrefix: "s3://bucket/logs",
			want:       nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := generateLogShards(tt.hours, tt.components, tt.clusterID, tt.destPrefix)
			if len(got) != len(tt.want) {
				t.Fatalf("generateLogShards() returned %d shards, want %d", len(got), len(tt.want))
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("shard[%d]:\n  got  %+v\n  want %+v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func TestGenerateDeltalakeShards(t *testing.T) {
	tests := []struct {
		name       string
		dates      []string
		components []string
		orgID      string
		clusterID  string
		destPrefix string
		want       []FileShard
	}{
		{
			name:       "single date single component",
			dates:      []string{"20250115"},
			components: []string{"slow_query"},
			orgID:      "org1",
			clusterID:  "c123",
			destPrefix: "s3://bucket/delta",
			want: []FileShard{
				{
					Index:        0,
					SourcePrefix: "deltalake/org1/c123/slow_query/date=20250115",
					DestPrefix:   "s3://bucket/delta/org1/c123/slow_query/date=20250115",
					Date:         "20250115",
					Component:    "slow_query",
					ClusterID:    "c123",
				},
			},
		},
		{
			name:       "single date multiple components",
			dates:      []string{"20250115"},
			components: []string{"slow_query", "statements"},
			orgID:      "org2",
			clusterID:  "c456",
			destPrefix: "s3://bucket/delta",
			want: []FileShard{
				{
					Index:        0,
					SourcePrefix: "deltalake/org2/c456/slow_query/date=20250115",
					DestPrefix:   "s3://bucket/delta/org2/c456/slow_query/date=20250115",
					Date:         "20250115",
					Component:    "slow_query",
					ClusterID:    "c456",
				},
				{
					Index:        1,
					SourcePrefix: "deltalake/org2/c456/statements/date=20250115",
					DestPrefix:   "s3://bucket/delta/org2/c456/statements/date=20250115",
					Date:         "20250115",
					Component:    "statements",
					ClusterID:    "c456",
				},
			},
		},
		{
			name:       "multiple dates multiple components",
			dates:      []string{"20250115", "20250116"},
			components: []string{"slow_query", "statements"},
			orgID:      "org3",
			clusterID:  "c789",
			destPrefix: "s3://bucket/delta",
			want: []FileShard{
				{
					Index:        0,
					SourcePrefix: "deltalake/org3/c789/slow_query/date=20250115",
					DestPrefix:   "s3://bucket/delta/org3/c789/slow_query/date=20250115",
					Date:         "20250115",
					Component:    "slow_query",
					ClusterID:    "c789",
				},
				{
					Index:        1,
					SourcePrefix: "deltalake/org3/c789/statements/date=20250115",
					DestPrefix:   "s3://bucket/delta/org3/c789/statements/date=20250115",
					Date:         "20250115",
					Component:    "statements",
					ClusterID:    "c789",
				},
				{
					Index:        2,
					SourcePrefix: "deltalake/org3/c789/slow_query/date=20250116",
					DestPrefix:   "s3://bucket/delta/org3/c789/slow_query/date=20250116",
					Date:         "20250116",
					Component:    "slow_query",
					ClusterID:    "c789",
				},
				{
					Index:        3,
					SourcePrefix: "deltalake/org3/c789/statements/date=20250116",
					DestPrefix:   "s3://bucket/delta/org3/c789/statements/date=20250116",
					Date:         "20250116",
					Component:    "statements",
					ClusterID:    "c789",
				},
			},
		},
		{
			name:       "empty inputs",
			dates:      nil,
			components: nil,
			orgID:      "",
			clusterID:  "",
			destPrefix: "",
			want:       nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := generateDeltalakeShards(tt.dates, tt.components, tt.orgID, tt.clusterID, tt.destPrefix)
			if len(got) != len(tt.want) {
				t.Fatalf("generateDeltalakeShards() returned %d shards, want %d", len(got), len(tt.want))
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("shard[%d]:\n  got  %+v\n  want %+v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func mustParseTime(t *testing.T, s string) time.Time {
	tv, err := time.Parse(time.RFC3339, s)
	if err != nil {
		t.Fatalf("parse time %q: %v", s, err)
	}
	return tv
}
