package db

import (
	"errors"
	"fmt"
	"time"

	"github.com/pingcap/agent-git-service/internal/tenant"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

var (
	// ErrTenantOwnershipMismatch means the database was already claimed by a
	// different tenant and must not be used for the requested tenant.
	ErrTenantOwnershipMismatch = errors.New("database tenant ownership mismatch")
	// ErrTenantMetadataCorrupt means the singleton ownership invariant is not
	// true and startup cannot safely select this database.
	ErrTenantMetadataCorrupt = errors.New("database tenant metadata is corrupt")
)

// TenantMetadata permanently binds one database to one AGS tenant.
type TenantMetadata struct {
	SingletonID   uint8     `gorm:"column:singleton_id;primaryKey;autoIncrement:false"`
	TenantID      string    `gorm:"column:tenant_id;size:64;not null;uniqueIndex"`
	InitializedAt time.Time `gorm:"column:initialized_at;not null"`
}

func (TenantMetadata) TableName() string { return "ags_tenant_metadata" }

// ClaimTenant atomically claims the configured database or verifies its
// existing ownership record. Configuration is authoritative for databases
// that predate tenant ownership metadata.
func ClaimTenant(database *gorm.DB, requested tenant.ID) error {
	if database == nil {
		return fmt.Errorf("claim tenant: nil database")
	}
	normalized, err := tenant.ParseID(string(requested))
	if err != nil || normalized != requested {
		return fmt.Errorf("claim tenant: %w", tenant.ErrInvalid)
	}
	hasMetadataTable := database.Migrator().HasTable(&TenantMetadata{})
	if hasMetadataTable {
		var count int64
		if err := database.Model(&TenantMetadata{}).Count(&count).Error; err != nil {
			return fmt.Errorf("inspect tenant metadata: %w", err)
		}
		if count > 1 {
			return fmt.Errorf("%w: expected at most one ownership record, found %d", ErrTenantMetadataCorrupt, count)
		}
	}
	if err := database.AutoMigrate(&TenantMetadata{}); err != nil {
		return fmt.Errorf("prepare tenant ownership metadata: %w", err)
	}

	return database.Transaction(func(tx *gorm.DB) error {
		record := TenantMetadata{
			SingletonID:   1,
			TenantID:      string(requested),
			InitializedAt: time.Now().UTC(),
		}
		if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&record).Error; err != nil {
			return fmt.Errorf("claim tenant %q: %w", requested, err)
		}

		var stored TenantMetadata
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			First(&stored, "singleton_id = ?", 1).Error; err != nil {
			return fmt.Errorf("verify tenant %q: %w", requested, err)
		}
		if stored.SingletonID != 1 || stored.TenantID == "" {
			return fmt.Errorf("%w: invalid singleton record", ErrTenantMetadataCorrupt)
		}
		if tenant.ID(stored.TenantID) != requested {
			return fmt.Errorf("%w: database belongs to %q, requested %q", ErrTenantOwnershipMismatch, stored.TenantID, requested)
		}

		var count int64
		if err := tx.Model(&TenantMetadata{}).Count(&count).Error; err != nil {
			return fmt.Errorf("verify tenant metadata count: %w", err)
		}
		if count != 1 {
			return fmt.Errorf("%w: expected one ownership record, found %d", ErrTenantMetadataCorrupt, count)
		}
		return nil
	})
}
