package billing

import (
	"context"

	"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"
	"github.com/pingcap/dbaas/central/handler/billing2"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	mailv2 "github.com/tidbcloud/pkgv2/mail/v2"
	"github.com/tidbcloud/pkgv2/salesforce"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Model struct {
	log              *zap.Logger
	mail             mailv2.Facade
	salesforceClient SalesforceHandler
}

type Credit struct {
	ID      uint64
	Amount  int64
	Created int64
}

type SalesforceHandler interface {
	UpdateAccount(account salesforce.Account) error
	SyncLeads(lead salesforce.Lead) error
}

func NewModel(log *zap.Logger, mail mailv2.Facade, salesforceClient SalesforceHandler) Model {
	return Model{log: log, mail: mail, salesforceClient: salesforceClient}
}

type Context struct {
	Model
	tenantAndUser *tenant.User
	log           *zap.Logger
}

func (c Context) Log() *zap.Logger {
	return c.log
}

// User returns a UserContext
func (m Model) User(user *tenant.User) Context {
	return Context{m, user, m.log.With(zap.Uint64("tenantID", uint64(user.TenantID)), zap.Uint64("userID", uint64(user.ID)), zap.String("module", "billing"))}
}

func (c Context) CheckOrgID(orgID entity.ID) error {
	if c.tenantAndUser.IsImpersonated {
		return nil
	}
	if orgID != c.tenantAndUser.TenantID {
		return errcode.NewForbiddenErr(errors.New("org_id parameter is inconsistent with the user's org ID"))
	}
	return nil
}

func (c Context) CheckIfOwnerOrBillingAdmin() error {
	user := c.tenantAndUser
	if user.Role == entity.UserRoleOwner || user.Role == entity.UserRoleBillingAdmin {
		return nil
	}
	return errcode.NewForbiddenErr(errors.New("only owner or billing admin can perform this operation"))
}

func (c Context) GetTenantEntity() (*entity.Tenant, error) {
	var t entity.Tenant

	err := db.Transaction(func(tx *gorm.DB) error {
		var err error
		t, err = dbutil.GetTenantByID(tx, c.tenantAndUser.TenantID)
		return err
	})

	if err != nil {
		return nil, err
	}
	return &t, nil
}

func (c Context) GetTenantID() entity.ID {
	return c.tenantAndUser.TenantID
}

func (c Context) GetAdminUser(email string) (*entity.User, error) {
	var user entity.User
	var err error
	err = db.Transaction(func(tx *gorm.DB) error {
		user, err = dbutil.GetUserByEmail(tx, email)
		return err
	})
	if err != nil {
		c.Log().Error("trial worker: failed to get admin user of tenant", zap.Error(err))
		return nil, err
	}
	return &user, nil
}

func (c Context) GetMailHandler() mailv2.Facade {
	return c.mail
}

func (c Context) GetDB() *gorm.DB {
	ctx := context.Background()
	dbProvider := db.NewProvider()
	tx := dbProvider.WriteDB(ctx)
	return tx
}

func (c Context) MeasureCosts(cost *billing2.UnitCostsInput) error {
	return billing2.GetHandler().MeasureCosts(c.GetTenantID().Uint64(), cost)
}
