package service_test

import (
	"context"
	"fmt"
	"testing"
	"time"

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

	"github.com/prometheus/client_golang/prometheus/testutil"
	"gorm.io/gorm"
)

func TestBusinessMetricsActivity_RecordsDailyAndFirstPushOnce(t *testing.T) {
	svc, cleanup := testharness.NewService(t, testharness.ServiceConfig{})
	defer cleanup()

	now := time.Now().UTC()
	user := db.User{
		Login:     "business-metrics-user",
		Name:      "business-metrics-user",
		Type:      db.TypeUser,
		UserKind:  db.UserKindHuman,
		CreatedAt: now.Add(-2 * time.Hour),
	}
	if err := svc.DB.Create(&user).Error; err != nil {
		t.Fatalf("create user: %v", err)
	}
	repo := db.Repository{
		Name:          "business-metrics-repo",
		FullName:      user.Login + "/business-metrics-repo",
		OwnerID:       user.ID,
		DefaultBranch: "main",
		CreatedAt:     now.Add(-1 * time.Hour),
		UpdatedAt:     now.Add(-1 * time.Hour),
	}
	if err := svc.DB.Create(&repo).Error; err != nil {
		t.Fatalf("create repo: %v", err)
	}

	ctx := service.ContextWithUser(context.Background(), user)
	if err := svc.UpdateRepositoryPushedAt(ctx, repo.ID, now); err != nil {
		t.Fatalf("first UpdateRepositoryPushedAt: %v", err)
	}
	if err := svc.UpdateRepositoryPushedAt(ctx, repo.ID, now.Add(time.Minute)); err != nil {
		t.Fatalf("second UpdateRepositoryPushedAt: %v", err)
	}

	day := now.UTC().Format("2006-01-02")
	assertBusinessCount(t, svc, &db.BusinessUserActivityDaily{}, 1, "day = ? AND user_id = ?", day, user.ID)
	assertBusinessCount(t, svc, &db.BusinessRepoActivityDaily{}, 1, "day = ? AND repository_id = ?", day, repo.ID)
	assertBusinessCount(t, svc, &db.BusinessFirstUserAction{}, 1, "user_id = ? AND action = ?", user.ID, "git_push")
	assertBusinessCount(t, svc, &db.BusinessFirstRepoAction{}, 1, "repository_id = ? AND action = ?", repo.ID, "git_push")
}

func TestBusinessMetricsActivity_DeleteRepoRefreshesActiveRepoGauge(t *testing.T) {
	metrics.Init()

	svc, cleanup := testharness.NewService(t, testharness.ServiceConfig{})
	defer cleanup()

	now := time.Now().UTC()
	day := now.Format("2006-01-02")

	owner := db.User{Login: "repo-gauge-owner", Name: "repo-gauge-owner", Type: db.TypeUser}
	other := db.User{Login: "repo-gauge-other", Name: "repo-gauge-other", Type: db.TypeUser}
	if err := svc.DB.Create(&owner).Error; err != nil {
		t.Fatalf("create owner: %v", err)
	}
	if err := svc.DB.Create(&other).Error; err != nil {
		t.Fatalf("create other: %v", err)
	}

	repo := db.Repository{Name: "repo-gauge", FullName: owner.Login + "/repo-gauge", OwnerID: owner.ID, DefaultBranch: "main", CreatedAt: now, UpdatedAt: now}
	otherRepo := db.Repository{Name: "repo-gauge-other", FullName: other.Login + "/repo-gauge-other", OwnerID: other.ID, DefaultBranch: "main", CreatedAt: now, UpdatedAt: now}
	if err := svc.DB.Create(&repo).Error; err != nil {
		t.Fatalf("create repo: %v", err)
	}
	if err := svc.DB.Create(&otherRepo).Error; err != nil {
		t.Fatalf("create other repo: %v", err)
	}

	if err := svc.DB.Create(&db.BusinessRepoActivityDaily{Day: day, RepositoryID: repo.ID}).Error; err != nil {
		t.Fatalf("create business activity for repo: %v", err)
	}
	if err := svc.DB.Create(&db.BusinessRepoActivityDaily{Day: day, RepositoryID: otherRepo.ID}).Error; err != nil {
		t.Fatalf("create business activity for other repo: %v", err)
	}

	ownerCtx := service.ContextWithUser(context.Background(), owner)
	otherCtx := service.ContextWithUser(context.Background(), other)

	if err := svc.UpdateRepositoryPushedAt(ownerCtx, repo.ID, now); err != nil {
		t.Fatalf("record push for repo: %v", err)
	}
	if err := svc.UpdateRepositoryPushedAt(otherCtx, otherRepo.ID, now); err != nil {
		t.Fatalf("record push for other repo: %v", err)
	}

	if got := testutil.ToFloat64(metrics.DefaultPrometheus().ActiveReposDaily); got != 2 {
		t.Fatalf("active_repos_daily before delete: got %v, want 2", got)
	}

	if err := svc.DeleteRepo(ownerCtx, repo.FullName); err != nil {
		t.Fatalf("delete repo: %v", err)
	}

	if got := testutil.ToFloat64(metrics.DefaultPrometheus().ActiveReposDaily); got != 1 {
		t.Fatalf("active_repos_daily after delete: got %v, want 1", got)
	}
}

func TestBusinessMetricsActivity_TenantDBDoesNotOverwriteGlobalDailyGauge(t *testing.T) {
	metrics.Init()

	svc, cleanup := testharness.NewService(t, testharness.ServiceConfig{})
	defer cleanup()

	now := time.Now().UTC()
	day := now.Format("2006-01-02")

	defaultOwner := db.User{Login: "default-owner", Name: "default-owner", Type: db.TypeUser}
	if err := svc.DB.Create(&defaultOwner).Error; err != nil {
		t.Fatalf("create default owner: %v", err)
	}
	defaultCtx := service.ContextWithUser(context.Background(), defaultOwner)
	for i := 0; i < 2; i++ {
		repo := db.Repository{
			Name:          fmt.Sprintf("default-repo-%d", i),
			FullName:      fmt.Sprintf("%s/default-repo-%d", defaultOwner.Login, i),
			OwnerID:       defaultOwner.ID,
			DefaultBranch: "main",
			CreatedAt:     now,
			UpdatedAt:     now,
		}
		if err := svc.DB.Create(&repo).Error; err != nil {
			t.Fatalf("create default repo %d: %v", i, err)
		}
		if err := svc.UpdateRepositoryPushedAt(defaultCtx, repo.ID, now); err != nil {
			t.Fatalf("record default repo %d push: %v", i, err)
		}
	}
	if got := testutil.ToFloat64(metrics.DefaultPrometheus().ActiveReposDaily); got != 2 {
		t.Fatalf("active_repos_daily after default DB activity: got %v, want 2", got)
	}

	tenantDB := openBusinessMetricsTenantDB(t)
	tenantOwner := db.User{Login: "tenant-owner", Name: "tenant-owner", Type: db.TypeUser}
	if err := tenantDB.Create(&tenantOwner).Error; err != nil {
		t.Fatalf("create tenant owner: %v", err)
	}
	tenantRepo := db.Repository{
		Name:          "tenant-repo",
		FullName:      tenantOwner.Login + "/tenant-repo",
		OwnerID:       tenantOwner.ID,
		DefaultBranch: "main",
		CreatedAt:     now,
		UpdatedAt:     now,
	}
	if err := tenantDB.Create(&tenantRepo).Error; err != nil {
		t.Fatalf("create tenant repo: %v", err)
	}
	if err := tenantDB.Create(&db.BusinessRepoActivityDaily{Day: day, RepositoryID: tenantRepo.ID}).Error; err != nil {
		t.Fatalf("seed tenant business activity: %v", err)
	}
	tenantCtx := service.ContextWithDB(service.ContextWithUser(context.Background(), tenantOwner), tenantDB)
	if err := svc.UpdateRepositoryPushedAt(tenantCtx, tenantRepo.ID, now); err != nil {
		t.Fatalf("record tenant repo push: %v", err)
	}

	if got := testutil.ToFloat64(metrics.DefaultPrometheus().ActiveReposDaily); got != 2 {
		t.Fatalf("active_repos_daily after tenant DB activity: got %v, want 2", got)
	}
}

func openBusinessMetricsTenantDB(t *testing.T) *gorm.DB {
	t.Helper()
	return openMigratedServiceTestDB(t)
}

func assertBusinessCount(t *testing.T, svc *service.Service, model any, want int64, query string, args ...any) {
	t.Helper()
	var got int64
	if err := svc.DB.Model(model).Where(query, args...).Count(&got).Error; err != nil {
		t.Fatalf("count %T: %v", model, err)
	}
	if got != want {
		t.Fatalf("count %T: got %d, want %d", model, got, want)
	}
}
