package org

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/tidbcloud/account/internel/dao/entity"
)

var _ OrgDaoIface = (*OrgDao)(nil)

type OrgDaoIface interface {
	GetOrgByOrgID(ctx context.Context, tx *gorm.DB, orgID uint64) (*entity.Tenant, error)
	ListOrgs(ctx context.Context, tx *gorm.DB, param ListOrgsParam) (*ListOrgsResult, error)
	ListOrgsByFilter(ctx context.Context, db *gorm.DB, filter ListOrgsFilter) ([]entity.Tenant, error)
	ListTrials(ctx context.Context, tx *gorm.DB, orgID uint64, trialType string) ([]entity.Trial, error)
	CreateTrial(ctx context.Context, tx *gorm.DB, creatorUserID uint64, orgID uint64, startedAt time.Time, estimatedEndedAt *time.Time, endedAt *time.Time, trialType string) (*entity.Trial, error)
	MarkOrgAsDeleted(ctx context.Context, tx *gorm.DB, orgID uint64) error
	DeleteOrgImpersonates(ctx context.Context, tx *gorm.DB, orgID uint64) error
	DeleteOrgFromPartner(ctx context.Context, tx *gorm.DB, orgId uint64) error
	OpenOrgSso(ctx context.Context, tx *gorm.DB, orgID uint64, orgType, companyName string) error
	GetOrgByCompanyName(ctx context.Context, tx *gorm.DB, companyName string) (*entity.Tenant, error)
	UpdateOrg(ctx context.Context, tx *gorm.DB, param UpdateOrgParam) error
	CreateOrg(ctx context.Context, tx *gorm.DB, tenant entity.Tenant) (*entity.Tenant, error)
}

type OrgDao struct{}

func NewDao() OrgDaoIface {
	return &OrgDao{}
}

type ListOrgsParam struct {
	Filter ListOrgsFilter
	Search string `json:"search" form:"search"`
	SortBy string `json:"sort_by" form:"sort_by"`
	Order  string `json:"order" form:"order"`
	Limit  int    `json:"limit" form:"limit"`
	Offset int    `json:"offset" form:"offset"`
}

type ListOrgsFilter struct {
	ID             []uint64 `json:"id" form:"id"`
	Name           []string `json:"name" form:"name"`
	Status         []string `json:"status" form:"status"`
	Kind           []string `json:"kind" form:"kind"`
	AccountManager []string `json:"account_manager" form:"account_manager"`
	Country        []string `json:"country" form:"country"`
	OrgTag         []string `json:"org_tag" form:"org_tag"`
}

type UpdateOrgParam struct {
	Id                                uint64
	Name                              string
	Status                            entity.TenantStatus
	Email                             string
	Alias                             string
	ThirdPartyFreeTrial               *bool
	ThirdPartyAccountProvider         *string
	ThirdPartyAccountCode             *string
	OrgTag                            string
	OrgType                           *entity.OrgType
	Kind                              *entity.TenantKind
	IsUpdateThirdPartyAccountProvider bool
	IsUpdateThirdPartyAccountCode     bool
}

type ListOrgsResult struct {
	Total int64
	List  []entity.Tenant
}

func (d *OrgDao) GetOrgByOrgID(ctx context.Context, tx *gorm.DB, orgID uint64) (*entity.Tenant, error) {
	var org entity.Tenant
	err := entity.NewTenantQuerySet(tx).IDEq(entity.ID(orgID)).One(&org)

	if err != nil {
		if err != gorm.ErrRecordNotFound {
			tilog.Info(ctx, fmt.Sprintf("GetOrgByOrgID err org_id=%d", orgID), zap.Error(err))
		}
		return nil, err
	}
	//if err == gorm.ErrRecordNotFound {
	//	return nil, nil
	//}

	return &org, nil
}
func (d *OrgDao) ListOrgsByFilter(ctx context.Context, db *gorm.DB, filter ListOrgsFilter) ([]entity.Tenant, error) {
	var orgs []entity.Tenant
	tx := db.Model(&entity.Tenant{})
	if len(filter.ID) == 0 && len(filter.Status) == 0 && len(filter.Kind) == 0 && len(filter.AccountManager) == 0 && len(filter.Country) == 0 &&
		len(filter.OrgTag) == 0 && len(filter.Name) == 0 {
		return orgs, errs.NewBadRequest("ListOrgsByParam param must not empty", nil)
	}
	if len(filter.ID) > 0 {
		tx = tx.Where("id in (?)", filter.ID)
	}
	if len(filter.Name) > 0 {
		tx = tx.Where("name in (?)", filter.Name)
	}
	if len(filter.Status) > 0 {
		tx = tx.Where("status in (?)", filter.Status)
	}
	if len(filter.Kind) > 0 {
		tx = tx.Where("kind in (?)", filter.Kind)
	}
	if len(filter.AccountManager) > 0 {
		tx = tx.Where("account_manager in (?)", filter.AccountManager)
	}
	if len(filter.Country) > 0 {
		tx = tx.Where("country in (?)", filter.Country)
	}
	if len(filter.OrgTag) > 0 {
		tx = tx.Where("org_tag in (?)", filter.OrgTag)
	}

	tx = tx.Order("id asc")

	if err := tx.Find(&orgs).Error; err != nil && err != gorm.ErrRecordNotFound {
		tilog.Error(ctx, "ListOrgsByFilter err", zap.Any("filter", filter), zap.Error(err))
		return nil, err
	}

	return orgs, nil
}

func (d *OrgDao) ListOrgs(ctx context.Context, db *gorm.DB, param ListOrgsParam) (*ListOrgsResult, error) {
	// todo remove on next time  (add by wangletian )
	// After providing the paging query interface, add restrictions.
	/*
		if !config.IsProdEnv() {
			if len(orgIDs) == 0 && len(status) == 0 {
				return []entity.Tenant{}, errs.NewBadRequest("param is invalid", nil)
			}
		}
	*/

	tx := db.Model(&entity.Tenant{})
	filter := param.Filter
	if len(filter.ID) > 0 {
		tx = tx.Where("id in (?)", filter.ID)
	}
	if len(filter.Status) > 0 {
		tx = tx.Where("status in (?)", filter.Status)
	}
	if len(filter.Kind) > 0 {
		tx = tx.Where("kind in (?)", filter.Kind)
	}
	if len(filter.AccountManager) > 0 {
		tx = tx.Where("account_manager in (?)", filter.AccountManager)
	}
	if len(filter.Country) > 0 {
		tx = tx.Where("country in (?)", filter.Country)
	}
	if len(filter.OrgTag) > 0 {
		tx = tx.Where("org_tag in (?)", filter.OrgTag)
	}

	if len(param.Search) > 0 {
		tx = tx.Where(
			db.Where("id=?", param.Search).
				Or("lower(name) like ?", "%"+strings.ToLower(param.Search)+"%").
				Or("lower(email) like ?", "%"+strings.ToLower(param.Search)+"%").
				Or("lower(alias) like ?", "%"+strings.ToLower(param.Search)+"%"),
		)
	}

	var sortBy = "id"
	if param.SortBy != "" {
		sortBy = param.SortBy
	}
	var order = "asc"
	if param.Order != "" {
		order = param.Order
	}
	tx = tx.Order(fmt.Sprintf("%s %s", sortBy, order))

	var total int64
	if err := tx.Count(&total).Error; err != nil {
		return nil, err
	}
	if param.Limit > 0 && param.Offset >= 0 {
		tx = tx.Limit(param.Limit).Offset(param.Offset)
	}

	var orgs []entity.Tenant
	if err := tx.Find(&orgs).Error; err != nil {
		return nil, err
	}

	result := ListOrgsResult{
		Total: total,
		List:  orgs,
	}
	return &result, nil
}

func (d *OrgDao) ListTrials(ctx context.Context, tx *gorm.DB, orgID uint64, trialType string) ([]entity.Trial, error) {
	var trials []entity.Trial

	qs := entity.NewTrialQuerySet(tx)

	if orgID > 0 {
		qs = qs.TenantIDEq(entity.ID(orgID))
	}

	if trialType != "" {
		qs = qs.TrialTypeEq(entity.TrialType(trialType))
	}

	err := qs.All(&trials)
	if err != nil {
		return nil, err
	}

	return trials, nil
}

func (d *OrgDao) CreateTrial(ctx context.Context, tx *gorm.DB, creatorUserID uint64, orgID uint64, startedAt time.Time, estimatedEndedAt *time.Time, endedAt *time.Time, trialType string) (*entity.Trial, error) {
	trial := entity.Trial{
		CreatedByID:      entity.ID(creatorUserID),
		TenantID:         entity.ID(orgID),
		StartedAt:        startedAt,
		EstimatedEndedAt: estimatedEndedAt,
		EndedAt:          endedAt,
		TrialType:        entity.TrialType(trialType),
	}
	err := tx.Create(&trial).Error
	if err != nil {
		return nil, err
	}

	return &trial, nil
}

func (d *OrgDao) MarkOrgAsDeleted(ctx context.Context, tx *gorm.DB, orgID uint64) error {
	var org entity.Tenant
	err := entity.NewTenantQuerySet(tx).IDEq(entity.ID(orgID)).One(&org)
	if err != nil {
		return err
	}

	if org.Status != entity.TenantStatusActive {
		return errs.NewBadRequest("org is not active", nil)
	}

	org.Status = entity.TenantStatusDeleted
	return org.Update(tx, entity.TenantDBSchema.Status)
}

func (d *OrgDao) DeleteOrgImpersonates(ctx context.Context, tx *gorm.DB, orgID uint64) error {
	tilog.Info(ctx, "delete all impersonate records in org", zap.Uint64("orgID", orgID))
	return entity.NewImpersonatedUserQuerySet(tx.WithContext(ctx)).TenantIDEq(entity.ID(orgID)).Delete()
}

func (d *OrgDao) OpenOrgSso(ctx context.Context, tx *gorm.DB, orgID uint64, orgType, companyName string) error {
	dbNameToFieldName := map[string]interface{}{
		"org_type":     orgType,
		"company_name": companyName,
	}
	tx = tx.Model(&entity.Tenant{})
	err := tx.Where("id=?", orgID).Updates(dbNameToFieldName).Error
	if err != nil {
		tilog.Error(ctx, "OpenOrgSso failed "+err.Error())
		return err
	}
	return nil
}

func (d *OrgDao) GetOrgByCompanyName(ctx context.Context, tx *gorm.DB, companyName string) (*entity.Tenant, error) {
	var org entity.Tenant
	err := tx.Model(&entity.Tenant{}).Where("company_name=?", companyName).First(&org).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &org, nil
}

func (d *OrgDao) UpdateOrg(ctx context.Context, tx *gorm.DB, param UpdateOrgParam) error {
	tilog.Info(ctx, "UpdateOrg dao", zap.Any("UpdateOrg Param", param))
	updater := entity.NewTenantQuerySet(tx).IDEq(entity.ID(param.Id)).GetUpdater()
	if param.Name != "" {
		updater.SetName(param.Name)
	}

	if param.Email != "" {
		updater.SetEmail(param.Email)
	}
	if param.OrgTag != "" {
		updater.SetOrgTag(param.OrgTag)
	}
	if param.Kind != nil {
		updater.SetKind(*param.Kind)
	}
	if param.Alias != "" {
		updater.SetAlias(param.Alias)
	}
	if param.Status != "" {
		updater.SetStatus(param.Status)
	}

	if param.ThirdPartyFreeTrial != nil {
		updater.SetThirdPartyFreeTrial(*param.ThirdPartyFreeTrial)
	}
	if param.IsUpdateThirdPartyAccountCode {
		updater.SetThirdPartyAccountCode(param.ThirdPartyAccountCode)
	}
	if param.IsUpdateThirdPartyAccountProvider {
		updater.SetThirdPartyAccountProvider(param.ThirdPartyAccountProvider)
	}
	if param.OrgType != nil {
		updater.SetOrgType(*param.OrgType)
	}

	err := updater.Update()
	if err != nil {
		return err
	}
	return nil

}

func (d *OrgDao) DeleteOrgFromPartner(ctx context.Context, tx *gorm.DB, orgId uint64) error {
	if orgId == 0 {
		return errors.New("DeleteOrgFromPartner, tenant id must not null")
	}
	var tenant entity.Tenant

	err := entity.NewTenantQuerySet(tx).IDEq(entity.ID(orgId)).One(&tenant)
	if err != nil {
		return err
	}
	tenant.ThirdPartyAccountCode = nil
	tenant.ThirdPartyAccountProvider = nil
	tenant.OrgTag = ""

	return tenant.Update(tx, entity.TenantDBSchema.ThirdPartyAccountCode, entity.TenantDBSchema.ThirdPartyAccountProvider, entity.TenantDBSchema.OrgTag)

}

func (d *OrgDao) CreateOrg(ctx context.Context, tx *gorm.DB, tenant entity.Tenant) (*entity.Tenant, error) {

	err := tx.Create(&tenant).Error
	if err != nil {
		return nil, err
	}

	return &tenant, nil
}
