package repo

import (
	"context"
	"fmt"

	relent "github.com/tidbcloud/devops/entity/release"
	relmod "github.com/tidbcloud/devops/models/release"
	contutil "github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"

	"gorm.io/gorm"
)

type TicketDriver interface {
	Save(ctx context.Context, ticket *relent.ReleaseTicketInRepo) (*relent.ReleaseTicketInRepo, error)
	Load(ctx context.Context, name string) (*relent.ReleaseTicketInRepo, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*relent.ReleaseTicketInRepo, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
}

type ticketDriver driver

func (d *ticketDriver) Save(ctx context.Context, ticket *relent.ReleaseTicketInRepo) (*relent.ReleaseTicketInRepo, error) {
	wdb := d.dbProvider.WriteDB(ctx)
	ticketMod := d.cvter.EntityToModel.ReleaseTicket(ticket)

	// update association of github pr
	existPRLocs := contutil.TransformSlice(ticketMod.GithubPRLocations,
		func(i relmod.ReleaseTicketToGithubPR) string { return i.PRLocation })
	err := wdb.Where("name= ? AND NOT pr_location IN ?", ticket.Name, existPRLocs).
		Delete(&relmod.ReleaseTicketToGithubPR{}).
		Error
	if err != nil {
		return nil, fmt.Errorf("delete associated github pr of ticket %s failed: %w", ticket.Name, err)
	}

	err = wdb.Session(&gorm.Session{FullSaveAssociations: true}).Save(ticketMod).Error
	if err != nil {
		return nil, fmt.Errorf("save ticket %s failed: %w", ticket.Name, err)
	}

	// fill back id
	return d.cvter.ModelToEntity.ReleaseTicket(ticketMod), nil
}

func (d *ticketDriver) Load(ctx context.Context, name string) (*relent.ReleaseTicketInRepo, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	ticketMod := &relmod.ReleaseTicket{}

	err := d.buildModel(rdb).
		Where("name = ?", name).
		First(ticketMod).
		Error
	if err != nil {
		return nil, fmt.Errorf("load ticket %s failed: %w", name, err)
	}

	return d.cvter.ModelToEntity.ReleaseTicket(ticketMod), nil
}

func (d *ticketDriver) Query(ctx context.Context, opts ...repoopt.Option) ([]*relent.ReleaseTicketInRepo, error) {
	rdb := d.dbProvider.ReadDB(ctx)
	ticketMods := make([]*relmod.ReleaseTicket, 0)

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

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

	return contutil.TransformSlice(ticketMods, d.cvter.ModelToEntity.ReleaseTicket), nil
}

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

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

	return count, nil
}

func (d *ticketDriver) buildModel(db *gorm.DB) *gorm.DB {
	db = db.Model(&relmod.ReleaseTicket{}).Preload("GithubPRLocations")

	return db
}
