package flag

import (
	"context"
	"errors"
	"fmt"

	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/errs"
	"gorm.io/gorm"
)

const (
	featureFlagTableName    = "feature_flags"
	featureFlagOrgTableName = "feature_flag_orgs"
)

type FeatureFlagDaoIface interface {
	CreateFeatureFlag(ctx context.Context, tx *gorm.DB, flag *entity.FeatureFlag) (*entity.FeatureFlag, error)
	UpdateFeatureFlagByID(ctx context.Context, tx *gorm.DB, flag *entity.FeatureFlag) error
	DeleteFeatureFlagByID(ctx context.Context, tx *gorm.DB, id uint64) error
	DeleteFeatureFlagByName(ctx context.Context, tx *gorm.DB, name string) error
	GetFeatureFlagByName(ctx context.Context, tx *gorm.DB, name string) (*entity.FeatureFlag, error)

	CreateFeatureFlagOrg(ctx context.Context, tx *gorm.DB, flagOrg *entity.FeatureFlagOrg) (*entity.FeatureFlagOrg, error)
	BatchCreateFeatureFlagOrg(ctx context.Context, tx *gorm.DB, flagOrg []*entity.FeatureFlagOrg) error
	DeleteFeatureFlagOrgByFlagID(ctx context.Context, tx *gorm.DB, flagID uint64) error
	DeleteFeatureFlagOrgByFlagIDOrgID(ctx context.Context, tx *gorm.DB, flagID, orgID uint64) error
	ListFeatureFlagOrgByFlagID(ctx context.Context, tx *gorm.DB, flagID uint64) ([]*entity.FeatureFlagOrg, error)

	ListAllFeatureFlagsInBatches(ctx context.Context, tx *gorm.DB, batchSize int) ([]*entity.FeatureFlag, error)
	ListAllFeatureFlagOrgsInBatches(ctx context.Context, tx *gorm.DB, batchSize int) ([]*entity.FeatureFlagOrg, error)
	PageFeatureFlags(ctx context.Context, tx *gorm.DB, flagName string, offset, limit int) ([]*entity.FeatureFlag, int64, error)
	ListFeatureFlagOrgsByFlagIDs(ctx context.Context, tx *gorm.DB, flagIDs []uint64) ([]*entity.FeatureFlagOrg, error)
}

type FeatureFlagDao struct{}

func NewFeatureFlagDao() FeatureFlagDaoIface {
	return &FeatureFlagDao{}
}

func (dao *FeatureFlagDao) CreateFeatureFlag(ctx context.Context, tx *gorm.DB, flag *entity.FeatureFlag) (*entity.FeatureFlag, error) {
	err := tx.Table(featureFlagTableName).Create(flag).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, fmt.Sprintf("CreateFeatureFlag error: %+v", flag))
	}
	return flag, nil
}

func (dao *FeatureFlagDao) UpdateFeatureFlagByID(ctx context.Context, tx *gorm.DB, flag *entity.FeatureFlag) error {
	updateMap := map[string]interface{}{
		"description":  flag.Description,
		"email_suffix": flag.EmailSuffix,
		"min_org_id":   flag.MinOrgID,
	}

	err := tx.Table(featureFlagTableName).Where("id=?", flag.ID.Uint64()).Updates(updateMap).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("UpdateFeatureFlagByID: %+v", flag))
	}
	return nil
}

func (dao *FeatureFlagDao) DeleteFeatureFlagByID(ctx context.Context, tx *gorm.DB, id uint64) error {
	err := tx.Table(featureFlagTableName).Where("id=?", id).Delete(&entity.FeatureFlag{}).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return errs.WrapGORMError(err, fmt.Sprintf("DeleteFeatureFlagByID error: %d", id))
	}
	return nil
}

func (dao *FeatureFlagDao) DeleteFeatureFlagByName(ctx context.Context, tx *gorm.DB, name string) error {
	err := tx.Table(featureFlagTableName).Where("flag_name=?", name).Delete(&entity.FeatureFlag{}).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return errs.WrapGORMError(err, fmt.Sprintf("DeleteFeatureFlagByName error: %s", name))
	}
	return nil
}

func (dao *FeatureFlagDao) GetFeatureFlagByName(ctx context.Context, tx *gorm.DB, name string) (*entity.FeatureFlag, error) {
	var flag = &entity.FeatureFlag{}
	err := tx.Table(featureFlagTableName).Where("flag_name=?", name).First(flag).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, errs.WrapGORMError(err, fmt.Sprintf("GetFeatureFlagByName error: %s", name))
	}
	return flag, nil
}

func (dao *FeatureFlagDao) CreateFeatureFlagOrg(ctx context.Context, tx *gorm.DB, flagOrg *entity.FeatureFlagOrg) (*entity.FeatureFlagOrg, error) {
	err := tx.Table(featureFlagOrgTableName).Create(flagOrg).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, fmt.Sprintf("CreateFeatureFlagOrg error: %+v", flagOrg))
	}
	return flagOrg, nil
}

func (dao *FeatureFlagDao) BatchCreateFeatureFlagOrg(ctx context.Context, tx *gorm.DB, flagOrgs []*entity.FeatureFlagOrg) error {
	err := tx.Table(featureFlagOrgTableName).Create(&flagOrgs).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("BatchCreateFeatureFlagOrg error: %+v", flagOrgs))
	}
	return nil
}

func (dao *FeatureFlagDao) DeleteFeatureFlagOrgByFlagID(ctx context.Context, tx *gorm.DB, flagID uint64) error {
	err := tx.Table(featureFlagOrgTableName).Where("flag_id=?", flagID).Delete(&entity.FeatureFlagOrg{}).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return errs.WrapGORMError(err, fmt.Sprintf("DeleteFeatureFlagOrgByFlagID error: %d", flagID))
	}
	return nil
}

func (dao *FeatureFlagDao) DeleteFeatureFlagOrgByFlagIDOrgID(ctx context.Context, tx *gorm.DB, flagID, orgID uint64) error {
	err := tx.Table(featureFlagOrgTableName).Where("flag_id=? and org_id=?", flagID, orgID).Delete(&entity.FeatureFlagOrg{}).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return errs.WrapGORMError(err, fmt.Sprintf("DeleteFeatureFlagOrgByFlagIDOrgID error: %d, %d", flagID, orgID))
	}
	return nil
}

func (dao *FeatureFlagDao) ListFeatureFlagOrgByFlagID(ctx context.Context, tx *gorm.DB, flagID uint64) ([]*entity.FeatureFlagOrg, error) {
	var flagOrgs []*entity.FeatureFlagOrg
	err := tx.Table(featureFlagOrgTableName).Where("flag_id=?", flagID).Find(&flagOrgs).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, errs.WrapGORMError(err, fmt.Sprintf("ListFeatureFlagOrgByFlagID error: %d", flagID))
	}
	return flagOrgs, nil
}

func (dao *FeatureFlagDao) ListAllFeatureFlagsInBatches(ctx context.Context, tx *gorm.DB, batchSize int) ([]*entity.FeatureFlag, error) {
	var results, batchRets []*entity.FeatureFlag
	err := tx.Table(featureFlagTableName).FindInBatches(&batchRets, batchSize, func(tx *gorm.DB, batch int) error {
		results = append(results, batchRets...)
		return nil
	}).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, fmt.Sprintf("ListAllFeatureFlagsInBatches error: %+v", err))
	}
	return results, nil
}

func (dao *FeatureFlagDao) ListAllFeatureFlagOrgsInBatches(ctx context.Context, tx *gorm.DB, batchSize int) ([]*entity.FeatureFlagOrg, error) {
	var results, batchRets []*entity.FeatureFlagOrg
	err := tx.Table(featureFlagOrgTableName).FindInBatches(&batchRets, batchSize, func(tx *gorm.DB, batch int) error {
		results = append(results, batchRets...)
		return nil
	}).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, fmt.Sprintf("ListAllFeatureFlagOrgsInBatches error: %+v", err))
	}
	return results, nil
}

func (dao *FeatureFlagDao) PageFeatureFlags(ctx context.Context, tx *gorm.DB, flagName string, offset, limit int) ([]*entity.FeatureFlag, int64, error) {
	db := tx.Table(featureFlagTableName)
	if flagName != "" {
		db = db.Where("flag_name LIKE ?", "%"+flagName+"%")
	}
	var total int64
	err := db.Count(&total).Error
	if err != nil {
		return nil, 0, errs.WrapGORMError(err, "PageFeatureFlags count error")
	}
	var flags []*entity.FeatureFlag
	err = db.Offset(offset).Limit(limit).Order("created_at DESC").Find(&flags).Error
	if err != nil {
		return nil, 0, errs.WrapGORMError(err, "PageFeatureFlags find error")
	}
	return flags, total, nil
}

func (dao *FeatureFlagDao) ListFeatureFlagOrgsByFlagIDs(ctx context.Context, tx *gorm.DB, flagIDs []uint64) ([]*entity.FeatureFlagOrg, error) {
	if len(flagIDs) == 0 {
		return nil, nil
	}
	var flagOrgs []*entity.FeatureFlagOrg
	err := tx.Table(featureFlagOrgTableName).Where("flag_id IN ?", flagIDs).Find(&flagOrgs).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, errs.WrapGORMError(err, "ListFeatureFlagOrgsByFlagIDs error")
	}
	return flagOrgs, nil
}
