package admin

import (
	"context"
	"strings"

	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/dbutil"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/db/tenant"
	tenantModel "github.com/pingcap/dbaas/central/model/tenant"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"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
}

type ListTenantsReq struct {
	Filter  TenantFilter
	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(req *ListTenantsReq) ([]*Tenant, int64, error) {
	var (
		tenants []entity.Tenant
		total   int64
		err     error
	)

	err = db.Transaction(func(tx *gorm.DB) error {
		tenants, total, err = m.listTenants(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
		ctx := m.centralTenant.UserWithEntity(context.Background(), &tenant.User{
			TenantID: item.ID,
		}, &item)
		var org *tenantModel.Organization
		if req.Brief {
			org, err = ctx.GetOrganizationBrief()
		} else {
			org, err = ctx.GetOrganization()
		}
		if err != nil {
			return nil, 0, err
		}
		res := Tenant(*org)
		results = append(results, &res)
	}
	return results, total, nil
}

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

	tx = tx.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("name COLLATE utf8mb4_general_ci like ?", "%"+*filter.Name+"%")
	}
	if filter.Email != nil {
		tx = tx.Where("email COLLATE utf8mb4_general_ci like ?", "%"+*filter.Email+"%")
	}
	if filter.Alias != nil {
		tx = tx.Where("alias COLLATE utf8mb4_general_ci like ?", "%"+*filter.Alias+"%")
	}
	if len(filter.Status) != 0 {
		tx = tx.Where("status in (?)", filter.Status)
	}

	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)
	}

	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(tenantID uint64) error {
	return db.Transaction(func(tx *gorm.DB) error {
		clusterCount, err := dbutil.GetActiveClusterCount(tx, entity.ID(tenantID))
		if err != nil {
			return errors.Annotate(err, "get active cluster count")
		}
		if clusterCount > 0 {
			return errcode.NewInvalidInputErr(errors.Errorf("can't delete tenant, due to the tenant still has active clusters"))
		}
		return dbutil.UpdateTenantStatus(tx, entity.ID(tenantID), entity.TenantStatusDeleted)
	})
}

func (m UserContext) SalesforceSyncLeads(ctx context.Context, orgID entity.ID) error {
	return m.BillingContext(orgID.Uint64()).SyncSalesforce()
}

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

func (m UserContext) PatchTenant(req *PatchTenantReq) error {
	return db.Transaction(func(tx *gorm.DB) error {
		tenant, err := dbutil.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)
	})
}
