package service_test

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/service"
)

func TestRepositorySyncOverviewAndGlobalActivity(t *testing.T) {
	svc, tenantHandle, cleanup := setupTestService(t)
	defer cleanup()
	ctx := tenantHandle.Context()
	svc.SetRepositorySyncConnectionValidator(tenantHandle.Info().ID, allowRepositorySyncConnection{})

	owner := db.User{Login: "sync-overview-owner", Name: "sync-overview-owner", Type: db.TypeUser, UserKind: db.UserKindHuman, Status: db.UserStatusActive}
	outsider := db.User{Login: "sync-overview-outsider", Name: "sync-overview-outsider", Type: db.TypeUser, UserKind: db.UserKindHuman, Status: db.UserStatusActive}
	if err := svc.DBForCtx(ctx).Create(&owner).Error; err != nil {
		t.Fatalf("create owner: %v", err)
	}
	if err := svc.DBForCtx(ctx).Create(&outsider).Error; err != nil {
		t.Fatalf("create outsider: %v", err)
	}
	ownerCtx := testContextWithUser(ctx, owner)
	outsiderCtx := testContextWithUser(ctx, outsider)

	createBinding := func(userCtx context.Context, ownerLogin, repoName, githubID string) (db.SyncBinding, db.Repository) {
		t.Helper()
		repository, err := svc.CreateRepo(userCtx, service.CreateRepoInput{OwnerLogin: ownerLogin, Name: repoName})
		if err != nil {
			t.Fatalf("create repository %s/%s: %v", ownerLogin, repoName, err)
		}
		binding, err := svc.CreateRepositorySyncBinding(userCtx, service.CreateRepositorySyncBindingInput{
			AGSRepositoryID: repository.ID, GitHubRepositoryID: githubID,
			GitHubRepositoryFullName: "example/" + repoName, GitHubConnectionID: "github-app-installation:overview",
			InitialAuthority: service.SyncProviderAGS,
		})
		if err != nil {
			t.Fatalf("create binding for %s/%s: %v", ownerLogin, repoName, err)
		}
		return binding, repository
	}

	runningBinding, _ := createBinding(ownerCtx, owner.Login, "running", "R_overview_running")
	waitingBinding, _ := createBinding(ownerCtx, owner.Login, "waiting", "R_overview_waiting")
	attentionBinding, _ := createBinding(ownerCtx, owner.Login, "attention", "R_overview_attention")
	healthyBinding, _ := createBinding(ownerCtx, owner.Login, "healthy", "R_overview_healthy")
	outsiderBinding, _ := createBinding(outsiderCtx, outsider.Login, "private", "R_overview_private")

	runningRun, err := svc.CreateRepositorySyncRun(ownerCtx, runningBinding.ID, service.CreateRepositorySyncRunInput{})
	if err != nil {
		t.Fatalf("create running run: %v", err)
	}
	runItems := []db.SyncRunItem{
		{RunID: runningRun.ID, Phase: service.SyncRunPhaseSync, ObjectType: "issue", ObjectKey: "1", Action: "create", State: service.SyncRunItemStateCompleted},
		{RunID: runningRun.ID, Phase: service.SyncRunPhaseSync, ObjectType: "issue", ObjectKey: "2", Action: "create", State: service.SyncRunItemStatePlanned},
	}
	waitingRun, err := svc.CreateRepositorySyncRun(ownerCtx, waitingBinding.ID, service.CreateRepositorySyncRunInput{})
	if err != nil {
		t.Fatalf("create waiting run: %v", err)
	}
	retryAt := time.Now().UTC().Add(5 * time.Minute)
	if err := svc.DBForCtx(ctx).Model(&db.SyncRun{}).Where("id = ?", waitingRun.ID).Updates(map[string]any{
		"status": service.SyncRunStatusQueued, "stage": service.SyncRunStageQueued,
		"retry_after_at": &retryAt, "error_code": "rate_limited",
		"error_summary": db.LargeText("GitHub API rate limit exceeded"),
	}).Error; err != nil {
		t.Fatalf("mark waiting run: %v", err)
	}
	if err := svc.DBForCtx(ctx).Create(&runItems).Error; err != nil {
		t.Fatalf("create run items: %v", err)
	}

	attentionRun, err := svc.CreateRepositorySyncRun(ownerCtx, attentionBinding.ID, service.CreateRepositorySyncRunInput{})
	if err != nil {
		t.Fatalf("create attention run: %v", err)
	}
	now := time.Now().UTC()
	if err := svc.DBForCtx(ctx).Model(&db.SyncRun{}).Where("id = ?", attentionRun.ID).Updates(map[string]any{
		"status": service.SyncRunStatusFailed, "stage": service.SyncRunStageCompleted,
		"error_code": "permission_denied", "error_summary": db.LargeText("GitHub access is required"), "completed_at": &now,
	}).Error; err != nil {
		t.Fatalf("mark attention run failed: %v", err)
	}

	healthyRun, err := svc.CreateRepositorySyncRun(ownerCtx, healthyBinding.ID, service.CreateRepositorySyncRunInput{})
	if err != nil {
		t.Fatalf("create healthy run: %v", err)
	}
	if err := svc.DBForCtx(ctx).Model(&db.SyncRun{}).Where("id = ?", healthyRun.ID).Updates(map[string]any{
		"status": service.SyncRunStatusCompleted, "stage": service.SyncRunStageCompleted, "completed_at": &now,
	}).Error; err != nil {
		t.Fatalf("mark healthy run completed: %v", err)
	}
	if err := svc.DBForCtx(ctx).Model(&db.SyncBinding{}).Where("id = ?", healthyBinding.ID).Updates(map[string]any{
		"last_converged_run_id": healthyRun.ID, "last_sync_at": &now,
	}).Error; err != nil {
		t.Fatalf("mark healthy binding converged: %v", err)
	}
	if _, err := svc.CreateRepositorySyncRun(outsiderCtx, outsiderBinding.ID, service.CreateRepositorySyncRunInput{}); err != nil {
		t.Fatalf("create outsider run: %v", err)
	}

	overview, err := svc.GetRepositorySyncOverview(ownerCtx)
	if err != nil {
		t.Fatalf("GetRepositorySyncOverview: %v", err)
	}
	if overview.Summary.Connections != 4 || overview.Summary.Running != 1 || overview.Summary.WaitingForGitHub != 1 || overview.Summary.NeedsAttention != 1 || overview.Summary.Healthy != 1 {
		t.Fatalf("overview summary = %#v", overview.Summary)
	}
	if len(overview.Connections) != 4 {
		t.Fatalf("overview connections = %d, want 3", len(overview.Connections))
	}
	connections := make(map[uint]service.RepositorySyncOverviewConnection, len(overview.Connections))
	for _, connection := range overview.Connections {
		connections[connection.Binding.ID] = connection
	}
	if got := connections[runningBinding.ID]; got.Health != service.RepositorySyncHealthSyncing || got.ActiveRun == nil || got.Progress == nil || got.Progress.Total != 2 || got.Progress.Processed != 1 {
		t.Fatalf("running connection = %#v", got)
	}
	if got := connections[waitingBinding.ID]; got.Health != service.RepositorySyncHealthWaitingForGitHub || got.ActiveRun == nil || got.LatestRun == nil || got.LatestRun.RetryAfterAt == nil {
		t.Fatalf("waiting connection = %#v", got)
	}
	if got := connections[attentionBinding.ID]; got.Health != service.RepositorySyncHealthNeedsAttention || got.LatestRun == nil || got.LatestRun.ID != attentionRun.ID {
		t.Fatalf("attention connection = %#v", got)
	}
	if got := connections[healthyBinding.ID]; got.Health != service.RepositorySyncHealthHealthy {
		t.Fatalf("healthy connection = %#v", got)
	}

	firstPage, err := svc.ListRepositorySyncActivity(ownerCtx, service.ListRepositorySyncActivityInput{Limit: 2})
	if err != nil {
		t.Fatalf("ListRepositorySyncActivity first page: %v", err)
	}
	if len(firstPage.Items) != 2 || !firstPage.HasMore || firstPage.NextAfterID == 0 {
		t.Fatalf("first activity page = %#v", firstPage)
	}
	secondPage, err := svc.ListRepositorySyncActivity(ownerCtx, service.ListRepositorySyncActivityInput{Limit: 2, AfterID: firstPage.NextAfterID})
	if err != nil {
		t.Fatalf("ListRepositorySyncActivity second page: %v", err)
	}
	if len(secondPage.Items) != 2 || secondPage.HasMore {
		t.Fatalf("second activity page = %#v", secondPage)
	}
	activePage, err := svc.ListRepositorySyncActivity(ownerCtx, service.ListRepositorySyncActivityInput{Status: "active"})
	if err != nil {
		t.Fatalf("ListRepositorySyncActivity active: %v", err)
	}
	if len(activePage.Items) != 2 {
		t.Fatalf("active activity = %#v", activePage.Items)
	}
	if _, err := svc.GetRepositorySyncOverview(ctx); !errors.Is(err, service.ErrUnauthorized) {
		t.Fatalf("unauthenticated overview error = %v, want ErrUnauthorized", err)
	}
}
