package admin

import (
	"context"
	"strings"

	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/svc/org"
	"github.com/tidbcloud/account/internel/svc/org/tenantctx"
	"github.com/tidbcloud/account/internel/svc/user"

	"github.com/tidbcloud/account/commons/database"
	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/account/thirdparty/infra"

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/internel/dao/entity"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type (
	SorterOrder  string
	TenantSortBy string
)

const (
	OrderDesc SorterOrder = "desc"
	OrderAsc  SorterOrder = "asc"

	TenantSortByTimezone  TenantSortBy = "timezone"
	TenantSortByCreatedAt TenantSortBy = "created_at"
)

type Sorter struct {
	SortBy TenantSortBy
	Order  SorterOrder
}

type TenantFilter struct {
	ID     *entity.ID
	Plan   []entity.TenantPlan
	Kind   []entity.TenantKind
	Name   *string
	Alias  *string
	Email  *string
	Status []entity.TenantStatus
	OrgTag *string
}

type ListTenantsReq struct {
	Filter  TenantFilter
	Search  *string
	Sorter  Sorter
	Limit   int
	Offset  int
	NoLimit bool
	Brief   bool
}

func BuildSorter(sortBy, order string) Sorter {
	sorter := Sorter{}
	switch strings.ToLower(order) {
	case "desc":
		sorter.Order = OrderDesc
	case "asc":
		sorter.Order = OrderAsc
	default:
		sorter.Order = OrderDesc
	}
	sorter.SortBy = TenantSortBy(sortBy)
	return sorter
}

func (m UserContext) ListTenants(ctx context.Context, req *ListTenantsReq) ([]*Tenant, int64, error) {
	var (
		tenants []entity.Tenant
		total   int64
		err     error
	)

	err = database.Transaction(func(tx *gorm.DB) error {
		tenants, total, err = m.listTenants(ctx, tx, req)
		if err != nil {
			return err
		}

		if req.NoLimit {
			//no pagination
			total = int64(len(tenants))
		}
		return nil
	})
	if err != nil {
		return nil, 0, err
	}

	var results []*Tenant
	for _, item := range tenants {
		_ = item
		entctx := m.centralTenant.UserWithEntity(ctx, &user.User{
			TenantID: item.ID,
		}, &item)
		var org *tenantctx.Organization
		if req.Brief {
			org, err = entctx.GetOrganizationBrief()
		} else {
			org, err = entctx.GetOrganization(ctx)
		}
		if err != nil {
			return nil, 0, err
		}
		res := Tenant(*org)
		results = append(results, &res)
	}
	return results, total, nil
}

func (m UserContext) listTenants(ctx context.Context, db *gorm.DB, req *ListTenantsReq) ([]entity.Tenant, int64, error) {
	var (
		total   int64
		tenants []entity.Tenant
	)

	tx := db.Model(&entity.Tenant{})
	filter := req.Filter

	if filter.ID != nil {
		tx = tx.Where("id=?", filter.ID)
	}
	if len(filter.Plan) != 0 {
		tx = tx.Where("plan in (?)", filter.Plan)
	}
	if len(filter.Kind) != 0 {
		tx = tx.Where("kind in (?)", filter.Kind)
	}
	if filter.Name != nil {
		tx = tx.Where("lower(name) like ?", "%"+strings.ToLower(*filter.Name)+"%")
	}
	if filter.Email != nil {
		tx = tx.Where("lower(email) like ?", "%"+strings.ToLower(*filter.Email)+"%")
	}
	if filter.Alias != nil {
		tx = tx.Where("lower(alias) like ?", "%"+strings.ToLower(*filter.Alias)+"%")
	}

	if len(filter.Status) != 0 {
		tx = tx.Where("status in (?)", filter.Status)
	}
	if filter.OrgTag != nil {
		tx = tx.Where("org_tag = ?", filter.OrgTag)
	}
	tilog.Info(ctx, "listTenants", zap.Any("filter", filter))
	if req.Search != nil {
		tx = tx.Where(db.Where("id=?", *req.Search).
			Or("lower(name) like ?", "%"+strings.ToLower(*req.Search)+"%").
			Or("lower(email) like ?", "%"+strings.ToLower(*req.Search)+"%").
			Or("lower(alias) like ?", "%"+strings.ToLower(*req.Search)+"%"),
		)
	}

	var sortColumn string
	switch req.Sorter.SortBy {
	case TenantSortByTimezone:
		sortColumn = entity.TenantDBSchema.TimeZone.String()
	case TenantSortByCreatedAt:
		sortColumn = entity.TenantDBSchema.CreatedAt.String()
	default:
		// nothing
	}
	if sortColumn != "" {
		tx = tx.Order(clause.OrderByColumn{Column: clause.Column{Name: sortColumn}, Desc: req.Sorter.Order == OrderDesc})
	}

	if !req.NoLimit { //pagination
		if err := tx.Count(&total).Error; err != nil {
			return nil, 0, errors.Annotate(err, "count tenant error")
		}
		tx = tx.Limit(req.Limit).Offset(req.Offset)
	}
	tilog.Info(ctx, "listTenants", zap.Any("tx", tx))
	if err := tx.Find(&tenants).Error; err != nil {
		return nil, 0, errors.Annotate(err, "fetch tenants failed")
	}
	return tenants, total, nil
}

func (m UserContext) DeleteTenant(ctx context.Context, tenantID uint64) error {
	return database.Transaction(func(tx *gorm.DB) error {
		//clusterCount, err := dbutil.GetActiveClusterCount(tx, entity.ID(tenantID))
		clusterCount, err := infra.GetHandler().CountClustersByOrg(ctx, []uint64{tenantID})
		//
		if err != nil {
			return errors.Annotate(err, "get active cluster count")
		}
		if clusterCount > 0 {
			msg := "can't delete tenant, due to the tenant still has active clusters"
			tilog.Info(ctx, msg)
			return errcode.NewInvalidInputErr(errors.New(msg))
		}
		err = org.UpdateTenantStatus(tx, entity.ID(tenantID), entity.TenantStatusDeleted)
		if err != nil {
			return errors.Annotate(err, "update tenant status")
		}
		// just to delete role binding, not affect delete org
		_, err = dao.GetDaoHub().Role.DeleteRoleBindingByScopeInfo(ctx, tx, entity.RoleBindingScopeOrg, tenantID, "")
		if err != nil {
			tilog.Error(ctx, "failed to delete org relate role_bindings", zap.Uint64("orgID", tenantID), zap.Error(err))
		} else {
			tilog.Info(ctx, "delete org relate role_bindings success", zap.Uint64("orgID", tenantID))
		}
		return nil
	})
}

type PatchTenantReq struct {
	OrgID          uint64
	Kind           *string
	Alias          *string
	AccountManager *string
}

func (m UserContext) PatchTenant(req *PatchTenantReq) error {
	return database.Transaction(func(tx *gorm.DB) error {
		tenant, err := org.GetTenantByID(tx, entity.ID(req.OrgID))
		if err != nil {
			return err
		}
		if req.Kind != nil {
			kind := entity.TenantKind(*req.Kind)
			tenant.Kind = kind
		}
		if req.Alias != nil {
			tenant.Alias = *req.Alias
		}
		if req.AccountManager != nil {
			accountManager := strings.TrimSpace(*req.AccountManager)
			tenant.AccountManager = accountManager
		}
		return tenant.Update(tx, entity.TenantDBSchema.Kind, entity.TenantDBSchema.Alias, entity.TenantDBSchema.AccountManager)
	})
}
