package repo

import (
	"context"
	"fmt"

	ent "github.com/tidbcloud/devops/entity/github"
	mod "github.com/tidbcloud/devops/models/github"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"

	"gorm.io/gorm"
)

type RepoDriver interface {
	Save(ctx context.Context, repo *ent.RepositoryInDB) (*ent.RepositoryInDB, error)
	Load(ctx context.Context, id uint64) (*ent.RepositoryInDB, error)
	Query(ctx context.Context, opts ...repoopt.Option) ([]*ent.RepositoryInDB, error)
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
}

type repoDriver driver

func (r *repoDriver) Save(ctx context.Context, repo *ent.RepositoryInDB) (*ent.RepositoryInDB, error) {
	wdb := r.dbProvider.WriteDB(ctx)
	repoMod := r.cvter.EntityToModel().Repository(repo)

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

	// fill back id
	return r.cvter.ModelToEntity().Repository(repoMod), nil
}

func (r *repoDriver) Load(ctx context.Context, id uint64) (*ent.RepositoryInDB, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	repoMod := &mod.GithubRepository{}

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

	return r.cvter.ModelToEntity().Repository(repoMod), nil
}

func (r *repoDriver) Query(ctx context.Context, opts ...repoopt.Option) ([]*ent.RepositoryInDB, error) {
	rdb := r.dbProvider.ReadDB(ctx)
	repoMods := make([]*mod.GithubRepository, 0)

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

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

	return container.TransformSlice(repoMods, r.cvter.ModelToEntity().Repository), nil
}

func (r *repoDriver) Count(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	db := r.dbProvider.ReadDB(ctx).Model(&mod.GithubRepository{})
	db = repoopt.Aggregate(opts...).Apply(db)

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

	return count, nil
}

func (r *repoDriver) buildModel(db *gorm.DB) *gorm.DB {
	db = db.Model(&mod.GithubRepository{})

	return db
}
