package repo_imp

import (
	"context"
	"errors"
	"fmt"

	"github.com/tidbcloud/chaos-svc/model"
	"github.com/tidbcloud/chaos-svc/pkg/domain/constant"
	"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"
	"gorm.io/gorm"
)

type backendRepoImp struct {
	dbProvider db.Provider
}

func NewBackendRepo(dbProvider db.Provider) repo.BackendRepo {
	return &backendRepoImp{dbProvider: dbProvider}
}

func (b *backendRepoImp) Load(ctx context.Context, projectID uint64, networkName string) (*entity.Backend, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)
	backend := &model.ChaosBackend{}
	if err := buildBackendGetter(rdb).Where("project_id=? and network_name=?", projectID, networkName).First(backend).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, fmt.Sprintf("chaos backend not found, project: %d, network: %s", projectID, networkName))
		}
		return nil, bizerr.WrapGormError(err, "load chaos backend")
	}

	// find the last operation in the cluster
	var operation *model.ChaosBackendOperation
	if err := rdb.Model(&model.ChaosBackendOperation{}).
		Where("project_id=? and network_name=?", projectID, networkName).
		Order("created_at desc").
		First(&operation).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load backend operations")
	}
	type2Operation := map[constant.ChaosBackendOperationType]*model.ChaosBackendOperation{
		constant.ChaosBackendOperationType(operation.Type): operation,
	}
	return cvt.BackendModel2Entity(backend, type2Operation), nil
}

func (b *backendRepoImp) Save(ctx context.Context, backend *entity.Backend) bizerr.BizError {
	wdb := b.dbProvider.WriteDB(ctx)

	backendGroup := cvt.BackendEntity2Model(backend)
	operations := make([]*model.ChaosBackendOperation, 0, len(backendGroup.Type2Operation))
	for _, operation := range backendGroup.Type2Operation {
		operations = append(operations, operation)
	}
	if len(operations) > 0 {
		if err := wdb.Save(operations).Error; err != nil {
			return bizerr.WrapGormError(err, "save operations")
		}
	}
	if err := saveWithVersioning(wdb, backendGroup.Backend); err != nil {
		return err.Chain("save chaos backend")
	}
	return nil
}

func (b *backendRepoImp) QueryBackends(ctx context.Context, filter repo.BackendFilter) ([]*entity.Backend, bizerr.BizError) {
	rdb := b.dbProvider.ReadDB(ctx)

	var backends []*model.ChaosBackend
	if err := buildTxByBackendFilter(buildBackendGetter(rdb), filter).Find(&backends).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "find chaos backends")
	}

	out := make([]*entity.Backend, 0, len(backends))
	// TODO: maybe performance issue, need to improve
	for _, each := range backends {
		var operation *model.ChaosBackendOperation
		if err := rdb.Model(&model.ChaosBackendOperation{}).
			Where("project_id=? and network_name=?", each.ProjectID, each.NetworkName).
			Order("created_at desc").
			First(&operation).Error; err != nil {
			return nil, bizerr.WrapGormError(err, "load backend operations")
		}
		type2Operation := map[constant.ChaosBackendOperationType]*model.ChaosBackendOperation{
			constant.ChaosBackendOperationType(operation.Type): operation,
		}
		out = append(out, cvt.BackendModel2Entity(each, type2Operation))
	}

	return out, nil
}

func buildTxByBackendFilter(tx *gorm.DB, filter repo.BackendFilter) *gorm.DB {
	if len(filter.Statuses) != 0 {
		statuses := make([]model.ChaosBackendStatus, 0, len(filter.Statuses))
		for _, each := range filter.Statuses {
			statuses = append(statuses, cvt.BackendStatusPb2Model(each))
		}
		tx = tx.Where("status in (?)", statuses)
	}
	return tx
}

func buildBackendGetter(tx *gorm.DB) *gorm.DB {
	return tx.Model(&model.ChaosBackend{})
}
