package repo

import (
	"context"
	"fmt"

	ubent "github.com/tidbcloud/devops/entity/userbehavior"
	ubmod "github.com/tidbcloud/devops/models/userbehavior"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"gorm.io/gorm"
)

type SubscriptionDriver interface {
	Save(ctx context.Context, subscription *ubent.Subscription) (*ubent.Subscription, error)
	Patch(ctx context.Context, subscription *ubent.Subscription) (*ubent.Subscription, error)
	Load(ctx context.Context, id uint64) (*ubent.Subscription, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*ubent.Subscription, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
}

type subscriptionDriver driver

func (d *subscriptionDriver) Save(ctx context.Context, subscription *ubent.Subscription) (*ubent.Subscription, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	subMod := d.cvter.EntityToModel.Subscription(subscription)

	// don't update associated larkchats when save a subscription
	err := wdb.Session(&gorm.Session{FullSaveAssociations: false}).Save(subMod).Error
	if err != nil {
		return nil, fmt.Errorf("save subscription failed: %w", err)
	}

	// fill back id
	return d.cvter.ModelToEntity.Subscription(subMod), nil
}

func (d *subscriptionDriver) Patch(ctx context.Context, subscription *ubent.Subscription) (*ubent.Subscription, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	subMod := d.cvter.EntityToModel.Subscription(subscription)

	// don't update associated larkchats when patch a subscription
	err := wdb.Session(&gorm.Session{FullSaveAssociations: false}).Model(subMod).Updates(subMod).Error
	if err != nil {
		return nil, fmt.Errorf("patch subscription failed: %w", err)
	}

	// update associations
	err = wdb.Session(&gorm.Session{FullSaveAssociations: false}).Model(subMod).Association("LarkChats").Replace(subMod.LarkChats)
	if err != nil {
		return nil, fmt.Errorf("patch subscription failed when update larkchat associaitons: %w", err)
	}

	// fill back id
	return d.cvter.ModelToEntity.Subscription(subMod), nil
}

func (d *subscriptionDriver) Load(ctx context.Context, id uint64) (*ubent.Subscription, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	subMod := &ubmod.Subscription{}

	err := d.buildModel(rdb).Where("id = ?", id).First(subMod).Error
	if err != nil {
		return nil, fmt.Errorf("load subscription %d failed: %w", id, err)
	}

	return d.cvter.ModelToEntity.Subscription(subMod), nil
}

func (d *subscriptionDriver) Query(ctx context.Context, opts ...repoopt.Option) ([]*ubent.Subscription, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	subMods := make([]*ubmod.Subscription, 0)

	rdb = d.buildModel(rdb)
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	err := rdb.Find(&subMods).Error
	if err != nil {
		return nil, fmt.Errorf("query subscriptions failed: %w", err)
	}

	return container.TransformSlice(subMods, d.cvter.ModelToEntity.Subscription), nil
}

func (d *subscriptionDriver) Count(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	rdb := d.dbProvider.ReadDB(ctx).Model(&ubmod.Subscription{})
	rdb = repoopt.Aggregate(opts...).Apply(rdb)

	var count int64
	err := rdb.Count(&count).Error
	if err != nil {
		return 0, fmt.Errorf("count subscriptions failed: %w", err)
	}

	return count, nil
}

func (d *subscriptionDriver) buildModel(db *gorm.DB) *gorm.DB {
	db = db.Model(&ubmod.Subscription{}).Preload("LarkChats")

	return db
}
