package templateversion

import (
	"context"
	"sort"
	"testing"
	"time"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"gorm.io/gorm"
)

func TestIsUsageDetectableComponentTemplate(t *testing.T) {
	tests := []struct {
		name         string
		templateName string
		want         bool
	}{
		{
			name:         "usage-detectable component template",
			templateName: "tidb_group",
			want:         true,
		},
		{
			name:         "non-component template is not usage-detectable",
			templateName: "cluster_agent",
			want:         false,
		},
		{
			name:         "empty template name",
			templateName: "",
			want:         false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsUsageDetectableComponentTemplate(tt.templateName)
			if got != tt.want {
				t.Fatalf("IsUsageDetectableComponentTemplate(%q)=%v, want=%v", tt.templateName, got, tt.want)
			}
		})
	}
}

type fakeTemplateRepo struct {
	items []*entity.TemplateRegistry
}

func cloneRegistry(item *entity.TemplateRegistry) *entity.TemplateRegistry {
	return &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: item.GetTemplateName(),
			Version:      item.GetVersion(),
			Status:       item.GetStatus(),
			CreatedAt:    item.GetCreatedAt(),
		},
		Content: item.Content,
	}
}

func (r *fakeTemplateRepo) Upsert(_ context.Context, item *entity.TemplateRegistry) error {
	for _, existing := range r.items {
		if existing.GetTemplateName() == item.GetTemplateName() && existing.GetVersion() == item.GetVersion() {
			existing.Content = item.Content
			return nil
		}
	}
	c := cloneRegistry(item)
	if c.GetCreatedAt() == "" {
		c.CreatedAt = time.Now().Format(time.RFC3339)
	}
	r.items = append(r.items, c)
	return nil
}

func (r *fakeTemplateRepo) Load(_ context.Context, templateName, version string) (*entity.TemplateRegistry, error) {
	for _, item := range r.items {
		if item.GetTemplateName() == templateName && item.GetVersion() == version {
			return cloneRegistry(item), nil
		}
	}
	return nil, gormErrRecordNotFound
}

func (r *fakeTemplateRepo) ListByTemplateName(_ context.Context, templateName string) ([]*entity.TemplateRegistry, error) {
	var ret []*entity.TemplateRegistry
	for _, item := range r.items {
		if item.GetTemplateName() == templateName {
			ret = append(ret, cloneRegistry(item))
		}
	}
	sort.Slice(ret, func(i, j int) bool {
		return ret[i].GetCreatedAt() > ret[j].GetCreatedAt()
	})
	return ret, nil
}

func (r *fakeTemplateRepo) ListByTemplateNameForUpdate(ctx context.Context, templateName string) ([]*entity.TemplateRegistry, error) {
	return r.ListByTemplateName(ctx, templateName)
}

func (r *fakeTemplateRepo) UpdateStatus(_ context.Context, templateName, version string, status entity.TemplateVersionStatus) error {
	for _, item := range r.items {
		if item.GetTemplateName() == templateName && item.GetVersion() == version {
			item.Status = status
			return nil
		}
	}
	return gormErrRecordNotFound
}

func (r *fakeTemplateRepo) ListTemplateSummaries(ctx context.Context) ([]*repo.TemplateSummary, error) {
	seen := make(map[string]struct{})
	var names []string
	for _, item := range r.items {
		if _, ok := seen[item.GetTemplateName()]; !ok {
			seen[item.GetTemplateName()] = struct{}{}
			names = append(names, item.GetTemplateName())
		}
	}
	sort.Strings(names)
	summaries := make([]*repo.TemplateSummary, 0, len(names))
	for _, name := range names {
		items, err := r.ListByTemplateName(ctx, name)
		if err != nil {
			return nil, err
		}
		defaultVersion := ""
		for _, v := range items {
			if v.GetStatus() != apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE {
				continue
			}
			// ListByTemplateName returns items ordered by CreatedAt descending,
			// so the first AVAILABLE item is the newest AVAILABLE version.
			defaultVersion = v.GetVersion()
			break
		}
		summaries = append(summaries, &repo.TemplateSummary{
			TemplateName:   name,
			DefaultVersion: defaultVersion,
		})
	}
	return summaries, nil
}

func TestBootstrapNewVersionRemainsDefaultAfterOlderVersionRebootstrap(t *testing.T) {
	ctx := context.Background()
	const templateName = "tidb_group_hpa"
	oldContent := "old reverted content"
	featureContent := "feature content"
	bridgeContent := "rollback compatibility bridge"
	now := time.Now()

	fakeRepo := &fakeTemplateRepo{items: []*entity.TemplateRegistry{
		{
			TemplateVersionInfo: entity.TemplateVersionInfo{
				TemplateName: templateName,
				Version:      ComputeTemplateVersion(oldContent),
				Status:       statusAvailable,
				CreatedAt:    now.Add(-2 * time.Minute).Format(time.RFC3339),
			},
			Content: oldContent,
		},
		{
			TemplateVersionInfo: entity.TemplateVersionInfo{
				TemplateName: templateName,
				Version:      ComputeTemplateVersion(featureContent),
				Status:       statusAvailable,
				CreatedAt:    now.Add(-time.Minute).Format(time.RFC3339),
			},
			Content: featureContent,
		},
	}}
	svc := NewService(fakeRepo, 3)

	if err := svc.Bootstrap(ctx, map[string]string{templateName: bridgeContent}); err != nil {
		t.Fatalf("Bootstrap(bridge) returned error: %v", err)
	}
	bridgeVersion := ComputeTemplateVersion(bridgeContent)
	_, resolvedVersion, _, err := svc.Resolve(ctx, templateName, "")
	if err != nil {
		t.Fatalf("Resolve() after bridge returned error: %v", err)
	}
	if resolvedVersion != bridgeVersion {
		t.Fatalf("Resolve() version = %s, want bridge %s", resolvedVersion, bridgeVersion)
	}

	if err := svc.Bootstrap(ctx, map[string]string{templateName: featureContent}); err != nil {
		t.Fatalf("Bootstrap(feature) returned error: %v", err)
	}
	_, resolvedVersion, _, err = svc.Resolve(ctx, templateName, "")
	if err != nil {
		t.Fatalf("Resolve() after feature rebootstrap returned error: %v", err)
	}
	if resolvedVersion != bridgeVersion {
		t.Fatalf("Resolve() version = %s after feature rebootstrap, want bridge %s", resolvedVersion, bridgeVersion)
	}
}

func TestBootstrapKeepsExistingExpiredStatus(t *testing.T) {
	ctx := context.Background()
	templateName := "tidb_group"
	disabledContent := "v-disabled-content"
	now := time.Now()

	disabledVersion := ComputeTemplateVersion(NormalizeTemplateContent(disabledContent))
	v2 := &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: templateName,
			Version:      "v2",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
			CreatedAt:    now.Add(-time.Minute).Format(time.RFC3339),
		},
		Content: "v2-content",
	}
	v3 := &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: templateName,
			Version:      "v3",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
			CreatedAt:    now.Add(-2 * time.Minute).Format(time.RFC3339),
		},
		Content: "v3-content",
	}
	v4 := &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: templateName,
			Version:      "v4",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_EXPIRED,
			CreatedAt:    now.Add(-3 * time.Minute).Format(time.RFC3339),
		},
		Content: "v4-content",
	}

	fakeRepo := &fakeTemplateRepo{
		items: []*entity.TemplateRegistry{
			{
				TemplateVersionInfo: entity.TemplateVersionInfo{
					TemplateName: templateName,
					Version:      disabledVersion,
					Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_DISABLED,
					CreatedAt:    now.Format(time.RFC3339),
				},
				Content: disabledContent,
			},
			v2,
			v3,
			v4,
		},
	}
	svc := NewService(fakeRepo, 3)

	err := svc.Bootstrap(ctx, map[string]string{
		templateName: disabledContent,
	})
	if err != nil {
		t.Fatalf("Bootstrap() returned error: %v", err)
	}

	loadedV4, err := fakeRepo.Load(ctx, templateName, v4.GetVersion())
	if err != nil {
		t.Fatalf("failed to load v4: %v", err)
	}
	if loadedV4.GetStatus() != apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_EXPIRED {
		t.Fatalf("v4 status = %s, want EXPIRED", loadedV4.GetStatus())
	}
}

func TestBootstrapDoesNotExpireAvailableVersions(t *testing.T) {
	ctx := context.Background()
	templateName := "tidb_group"
	disabledContent := "v-disabled-content"
	now := time.Now()

	disabledVersion := ComputeTemplateVersion(NormalizeTemplateContent(disabledContent))
	v2 := &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: templateName,
			Version:      "v2",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
			CreatedAt:    now.Add(-time.Minute).Format(time.RFC3339),
		},
		Content: "v2-content",
	}
	v3 := &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: templateName,
			Version:      "v3",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
			CreatedAt:    now.Add(-2 * time.Minute).Format(time.RFC3339),
		},
		Content: "v3-content",
	}
	v4 := &entity.TemplateRegistry{
		TemplateVersionInfo: entity.TemplateVersionInfo{
			TemplateName: templateName,
			Version:      "v4",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
			CreatedAt:    now.Add(-3 * time.Minute).Format(time.RFC3339),
		},
		Content: "v4-content",
	}

	fakeRepo := &fakeTemplateRepo{
		items: []*entity.TemplateRegistry{
			{
				TemplateVersionInfo: entity.TemplateVersionInfo{
					TemplateName: templateName,
					Version:      disabledVersion,
					Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_DISABLED,
					CreatedAt:    now.Format(time.RFC3339),
				},
				Content: disabledContent,
			},
			v2,
			v3,
			v4,
		},
	}
	svc := NewService(fakeRepo, 3)

	err := svc.Bootstrap(ctx, map[string]string{
		templateName: disabledContent,
	})
	if err != nil {
		t.Fatalf("Bootstrap() returned error: %v", err)
	}

	loadedV4, err := fakeRepo.Load(ctx, templateName, v4.GetVersion())
	if err != nil {
		t.Fatalf("failed to load v4: %v", err)
	}
	if loadedV4.GetStatus() != apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE {
		t.Fatalf("v4 status = %s, want AVAILABLE", loadedV4.GetStatus())
	}
}

var gormErrRecordNotFound = gorm.ErrRecordNotFound

func TestListAllTemplates(t *testing.T) {
	ctx := context.Background()
	now := time.Now()

	tests := []struct {
		name  string
		items []*entity.TemplateRegistry
		want  []*repo.TemplateSummary
	}{
		{
			name: "multiple templates with default versions",
			items: []*entity.TemplateRegistry{
				{
					TemplateVersionInfo: entity.TemplateVersionInfo{
						TemplateName: "tidb_group",
						Version:      "aaa",
						Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
						CreatedAt:    now.Format(time.RFC3339),
					},
					Content: "c1",
				},
				{
					TemplateVersionInfo: entity.TemplateVersionInfo{
						TemplateName: "tikv_group",
						Version:      "bbb",
						Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
						CreatedAt:    now.Format(time.RFC3339),
					},
					Content: "c2",
				},
			},
			want: []*repo.TemplateSummary{
				{TemplateName: "tidb_group", DefaultVersion: "aaa"},
				{TemplateName: "tikv_group", DefaultVersion: "bbb"},
			},
		},
		{
			name: "default version is newest AVAILABLE",
			items: []*entity.TemplateRegistry{
				{
					TemplateVersionInfo: entity.TemplateVersionInfo{
						TemplateName: "tidb_group",
						Version:      "old",
						Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
						CreatedAt:    now.Add(-2 * time.Hour).Format(time.RFC3339),
					},
					Content: "old-c",
				},
				{
					TemplateVersionInfo: entity.TemplateVersionInfo{
						TemplateName: "tidb_group",
						Version:      "new",
						Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
						CreatedAt:    now.Format(time.RFC3339),
					},
					Content: "new-c",
				},
				{
					TemplateVersionInfo: entity.TemplateVersionInfo{
						TemplateName: "tidb_group",
						Version:      "disabled",
						Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_DISABLED,
						CreatedAt:    now.Add(time.Hour).Format(time.RFC3339),
					},
					Content: "dis-c",
				},
			},
			want: []*repo.TemplateSummary{
				{TemplateName: "tidb_group", DefaultVersion: "new"},
			},
		},
		{
			name: "no AVAILABLE version returns empty default",
			items: []*entity.TemplateRegistry{
				{
					TemplateVersionInfo: entity.TemplateVersionInfo{
						TemplateName: "cluster_agent",
						Version:      "exp",
						Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_EXPIRED,
						CreatedAt:    now.Format(time.RFC3339),
					},
					Content: "exp-c",
				},
			},
			want: []*repo.TemplateSummary{
				{TemplateName: "cluster_agent", DefaultVersion: ""},
			},
		},
		{
			name:  "empty repo returns empty list",
			items: nil,
			want:  []*repo.TemplateSummary{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeRepo := &fakeTemplateRepo{items: tt.items}
			svc := NewService(fakeRepo, 3)
			got, err := svc.ListAllTemplates(ctx)
			if err != nil {
				t.Fatalf("ListAllTemplates() error: %v", err)
			}
			if len(got) == 0 && len(tt.want) == 0 {
				return
			}
			if len(got) != len(tt.want) {
				t.Fatalf("ListAllTemplates() returned %d items, want %d", len(got), len(tt.want))
			}
			for i, w := range tt.want {
				if got[i].TemplateName != w.TemplateName {
					t.Errorf("[%d] TemplateName=%q, want %q", i, got[i].TemplateName, w.TemplateName)
				}
				if got[i].DefaultVersion != w.DefaultVersion {
					t.Errorf("[%d] DefaultVersion=%q, want %q", i, got[i].DefaultVersion, w.DefaultVersion)
				}
			}
		})
	}
}
