package repo_imp

import (
	"context"

	"github.com/tidbcloud/chaos-svc/model"
	"github.com/tidbcloud/chaos-svc/pkg/domain/entity"
	"github.com/tidbcloud/chaos-svc/pkg/domain/repo"
	"github.com/tidbcloud/chaos-svc/pkg/infra/repo_imp/cvt"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/db"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"gorm.io/gorm"
)

type workflowRepoImp struct {
	dbProvider db.Provider
}

func NewWorkflowRepo(dbProvider db.Provider) repo.WorkflowRepo {
	return &workflowRepoImp{dbProvider: dbProvider}
}

func (b *workflowRepoImp) Load(ctx context.Context, id uint64) (*entity.Workflow, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	workflow := &model.Workflow{}
	if err := buildWorkflowGetter(rdb).Where("id=?", id).First(workflow).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load chaos workflow")
	}

	var nodes []*model.WorkflowNode
	if err := rdb.Model(&model.WorkflowNode{}).
		Where("workflow_id=?", id).
		Find(&nodes).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load chaos workflow nodes")
	}
	nodeNameMap := make(map[string]*model.WorkflowNode)
	for _, node := range nodes {
		nodeNameMap[node.Name] = node
	}
	return cvt.WorkflowModel2Entity(workflow, nodeNameMap), nil
}

func (b *workflowRepoImp) Save(ctx context.Context, workflow *entity.Workflow) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)
	workflowGroup := cvt.WorkflowEntity2Model(workflow)

	if err := saveWithVersioning(wdb, workflowGroup.Workflow); err != nil {
		return err.Chain("save chaos workflow")
	}
	workflow.ID = workflowGroup.Workflow.ID.Uint64()
	workflow.CreatedAt = workflowGroup.Workflow.CreatedAt

	nodes := make([]*model.WorkflowNode, 0, len(workflow.Nodes))
	for _, node := range workflowGroup.Nodes {
		nodes = append(nodes, node)
	}
	if len(nodes) != 0 {
		if err := wdb.Save(nodes).Error; err != nil {
			return bizerr.WrapGormError(err, "save chaos workflow nodes")
		}
	}
	return nil
}

func (b *workflowRepoImp) Query(ctx context.Context, filter repo.WorkflowFilter, pager *base.Pager, workflowSorters []*repo.WorkflowSorter) ([]*entity.Workflow, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	sorters := make([]repo.Sorter, 0, len(workflowSorters))
	for _, sorter := range workflowSorters {
		sorters = append(sorters, sorter)
	}
	tx := buildTxByWorkflowFilter(buildWorkflowGetter(rdb), filter)
	tx = setOrderByIntoTx(setOffsetAndLimitIntoTx(tx, pager), sorters)

	var workflows []*model.Workflow
	if err := tx.Find(&workflows).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "query workflow")
	}
	if len(workflows) == 0 {
		return nil, nil
	}
	return b.buildWorkflows(ctx, workflows)
}

func (b *workflowRepoImp) Count(ctx context.Context, filter repo.WorkflowFilter) (int64, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	var count int64
	if err := buildTxByWorkflowFilter(buildWorkflowGetter(rdb), filter).Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "count workflow")
	}
	return count, nil
}

func (b *workflowRepoImp) buildWorkflows(ctx context.Context, workflows []*model.Workflow) ([]*entity.Workflow, bizerr.BizError) {
	if len(workflows) == 0 {
		return []*entity.Workflow{}, nil
	}
	rdb := b.dbProvider.ReadDB(ctx)
	workflowIDs := make([]uint64, 0, len(workflows))
	for _, workflow := range workflows {
		workflowIDs = append(workflowIDs, workflow.ID.Uint64())
	}

	var nodes []*model.WorkflowNode
	if err := rdb.Model(&model.WorkflowNode{}).
		Where("workflow_id in (?)", workflowIDs).
		Find(&nodes).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load chaos workflow nodes")
	}
	workflowID2Nodes := make(map[uint64]map[string]*model.WorkflowNode)
	for _, node := range nodes {
		workflowID := node.WorkflowID.Uint64()
		if workflowID2Nodes[workflowID] == nil {
			workflowID2Nodes[workflowID] = make(map[string]*model.WorkflowNode)
		}
		workflowID2Nodes[workflowID][node.Name] = node
	}

	workflowEntities := make([]*entity.Workflow, 0, len(workflows))
	for _, workflow := range workflows {
		workflowEntity := cvt.WorkflowModel2Entity(workflow, workflowID2Nodes[workflow.ID.Uint64()])
		workflowEntities = append(workflowEntities, workflowEntity)
	}
	return workflowEntities, nil
}

func buildWorkflowGetter(tx *gorm.DB) *gorm.DB {
	return tx.Model(&model.Workflow{})
}

func buildTxByWorkflowFilter(tx *gorm.DB, filter repo.WorkflowFilter) *gorm.DB {
	if len(filter.ProjectIDs) != 0 {
		tx = tx.Where("project_id in (?)", filter.ProjectIDs)
	}
	if len(filter.ClusterIDs) != 0 {
		tx = tx.Where("cluster_id in (?)", filter.ClusterIDs)
	}
	if len(filter.WorkflowIDs) != 0 {
		tx = tx.Where("id in (?)", filter.WorkflowIDs)
	}
	if filter.Deleted != nil {
		if *filter.Deleted {
			tx = tx.Where("deleted_at is not null")
		} else {
			tx = tx.Where("deleted_at is null")
		}
	}
	if len(filter.Statuses) != 0 {
		statuses := make([]model.ChaosBackendStatus, 0, len(filter.Statuses))
		for _, status := range filter.Statuses {
			statuses = append(statuses, model.ChaosBackendStatus(cvt.WorkflowStatusPb2Model(status)))
		}
		tx = tx.Where("status in (?)", statuses)
	}
	return tx
}
