package billing

import (
	"context"

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/idl/pbgen/proto/acl"
	"github.com/tidbcloud/dataflow-service/internal/db"
	"github.com/tidbcloud/dataflow-service/internal/db/entity"
	"github.com/tidbcloud/dataflow-service/pkg/account/accountdb"
	"github.com/tidbcloud/dataflow-service/pkg/account/accounttypes"
	"github.com/tidbcloud/dataflow-service/pkg/billing"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Model struct {
	log *zap.Logger
}

type Credit struct {
	ID      uint64
	Amount  int64
	Created int64
}

func NewModel(log *zap.Logger) Model {
	return Model{log: log}
}

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

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

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

func (c Context) CheckOrgID(orgID entity.ID) error {
	if c.tenantAndUser.IsImpersonated {
		return nil
	}
	if orgID.Uint64() != 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 == acl.OrgRole_ORG_ROLE_OWNER || user.Role == acl.OrgRole_ORG_ROLE_BILLING_ADMIN {
		return nil
	}
	return errcode.NewForbiddenErr(errors.New("only owner or billing admin can perform this operation"))
}

func (c Context) GetTenantEntity() (*accounttypes.Tenant, error) {
	t, err := accountdb.GetOrgByID(c.GetTenantID().Uint64())
	if err != nil {
		return nil, err
	}
	return t, nil
}

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

func (c Context) GetAdminUser(email string) (*accounttypes.User, error) {
	// FIXME: Admin User may be changed to other user, but luckily, this code may not be reached.
	user, err := accountdb.GetUserByEmail(email)
	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) GetDB() *gorm.DB {
	ctx := context.Background()
	dbProvider := db.NewProvider()
	tx := dbProvider.WriteDB(ctx)
	return tx
}

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