package service

import (
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/gitstore"
	"gorm.io/gorm"
)

func TestCreateReleaseReleasesRepositoryBeforePublishedMetric(t *testing.T) {
	database, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "release-lock.db")), &gorm.Config{})
	if err != nil {
		t.Fatalf("open database: %v", err)
	}
	if err := database.AutoMigrate(
		&db.User{}, &db.Repository{}, &db.Label{}, &db.RepositoryMigrationFreeze{},
		&db.RepositoryPushRecovery{}, &db.Release{}, &db.ReleaseAsset{},
		&db.BusinessRepoActivityDaily{},
	); err != nil {
		t.Fatalf("AutoMigrate: %v", err)
	}
	root := filepath.Join(t.TempDir(), "repositories")
	store, err := gitstore.New(root)
	if err != nil {
		t.Fatalf("gitstore.New: %v", err)
	}
	svc, err := New(Config{DB: database, Git: store})
	if err != nil {
		t.Fatalf("service.New: %v", err)
	}
	owner := db.User{Login: "release-owner", Type: db.TypeUser, SiteAdmin: true}
	if err := database.Create(&owner).Error; err != nil {
		t.Fatalf("create owner: %v", err)
	}
	repository := db.Repository{ID: 81, OwnerID: owner.ID, Name: "repo", FullName: "release-owner/repo", DefaultBranch: "main"}
	if err := database.Create(&repository).Error; err != nil {
		t.Fatalf("create repository: %v", err)
	}
	setup, err := svc.repositoryMutations.AcquireSession(t.Context(), repository.ID, repository.FullName)
	if err != nil {
		t.Fatalf("AcquireSession(setup): %v", err)
	}
	if err := setup.InitializeRepository(repository.DefaultBranch, true); err != nil {
		t.Fatalf("InitializeRepository: %v", err)
	}
	if err := setup.Release(); err != nil {
		t.Fatalf("Release(setup): %v", err)
	}

	metricStarted := make(chan struct{})
	metricContinue := make(chan struct{})
	var metricStartedOnce sync.Once
	continueMetric := sync.OnceFunc(func() { close(metricContinue) })
	defer continueMetric()
	const callbackName = "test:create_release_metric_block"
	if err := database.Callback().Create().Before("gorm:create").Register(callbackName, func(tx *gorm.DB) {
		if tx.Statement.Schema == nil || tx.Statement.Schema.Name != "BusinessRepoActivityDaily" {
			return
		}
		metricStartedOnce.Do(func() { close(metricStarted) })
		<-metricContinue
	}); err != nil {
		t.Fatalf("register metric callback: %v", err)
	}
	defer func() { _ = database.Callback().Create().Remove(callbackName) }()

	releaseDone := make(chan error, 1)
	go func() {
		_, err := svc.CreateRelease(t.Context(), repository.FullName, CreateReleaseInput{TagName: "v1"})
		releaseDone <- err
	}()
	select {
	case <-metricStarted:
	case <-time.After(5 * time.Second):
		t.Fatal("release metric did not start")
	}

	acquired := make(chan error, 1)
	go func() {
		session, err := svc.repositoryMutations.AcquireSession(t.Context(), repository.ID, repository.FullName)
		if err == nil {
			err = session.Release()
		}
		acquired <- err
	}()
	select {
	case err := <-acquired:
		if err != nil {
			t.Fatalf("AcquireSession while release metric blocked: %v", err)
		}
	case <-time.After(5 * time.Second):
		t.Fatal("repository writer remained held by release metric")
	}

	continueMetric()
	select {
	case err := <-releaseDone:
		if err != nil {
			t.Fatalf("CreateRelease: %v", err)
		}
	case <-time.After(5 * time.Second):
		t.Fatal("CreateRelease did not complete after metric release")
	}
}
