package admin

import (
	"context"

	"github.com/pingcap/errors"
	account_proto "github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/svc/admin/grafana"
	centralModel "github.com/tidbcloud/account/internel/svc/auditlog"
	centralTenantModel "github.com/tidbcloud/account/internel/svc/org/tenantctx"
	"github.com/tidbcloud/account/thirdparty/account"
	"github.com/tidbcloud/account/thirdparty/salesforce"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type GrafanaCollector interface {
	Search(ctx context.Context, params ...grafana.SearchParam) ([]grafana.FoundBoard, error)
}

type SalesForceMessenger interface {
	SyncLeads(ctx context.Context, lead salesforce.Lead) error
}

type Model struct {
	log              *zap.Logger
	grafanaClient    GrafanaCollector
	salesforceClient SalesForceMessenger
	centralTenant    centralTenantModel.Model
	accountAPI       account.AccountAPIHandler
}

type Project struct {
	ID   uint64
	Name string
}

type Tenant centralTenantModel.Organization

func NewModel(
	log *zap.Logger,
	grafanaClient GrafanaCollector,
	salesforceClient SalesForceMessenger,
	centralTenant centralTenantModel.Model,
	accountAPI account.AccountAPIHandler,
) Model {
	return Model{
		log:              log,
		grafanaClient:    grafanaClient,
		salesforceClient: salesforceClient,
		centralTenant:    centralTenant,
		accountAPI:       accountAPI,
	}
}

type UserContext struct {
	Model
	userEmail string
	log       *zap.Logger
}

// User returns a UserContext
func (m Model) User(ctx context.Context, userEmail string) UserContext {
	return UserContext{
		Model:     m,
		userEmail: userEmail,
		log:       m.log.With(zap.String("userID", userEmail), zap.String("model", "admin")).With(tilog.TraceIDFromContext(ctx)...),
	}
}

func (m UserContext) GetAccountUser(ctx context.Context) (*entity.User, error) {
	resp, err := m.accountAPI.GetUserByEmail(ctx, &account_proto.GetUserByEmailRequest{
		Email: m.userEmail,
	})
	if err != nil {
		return nil, errors.BadRequestf("can't get user: email=%s", m.userEmail)
	}
	if resp.User == nil {
		return nil, errors.BadRequestf("can't get user: email=%s", m.userEmail)
	}

	return &entity.User{
		Updatable: entity.Updatable{
			Base: entity.Base{
				ID: entity.ID(resp.User.Id),
			},
		},
		FirstName: resp.User.FirstName,
		LastName:  resp.User.LastName,
		Email:     resp.User.Email,
	}, nil
}

func (m UserContext) ListAccessLog(tenantID uint64, query *centralModel.ListAccessLogQuery) ([]centralModel.AccessLog, int64, error) {
	return centralModel.ListAuditLogs(entity.ID(tenantID), query)
}

type ImpersonateReq struct {
	OrgID uint64
}

func (m UserContext) AddImpersonation(ctx context.Context, req *ImpersonateReq) error {
	_, err := m.accountAPI.AddImpersonateUser(ctx,
		&account_proto.AddImpersonateUserReq{
			Email: m.userEmail,
			OrgId: req.OrgID,
		})
	if err != nil {
		return err
	}
	return nil
}

func (m UserContext) DeleteImpersonation(ctx context.Context, orgID uint64) error {
	_, err := m.accountAPI.DeleteImpersonateUser(ctx,
		&account_proto.DeleteImpersonateUserReq{
			Email: m.userEmail,
			OrgId: orgID,
		})
	if err != nil {
		return err
	}
	return nil
}
