package privilege

import (
	"context"

	privent "github.com/tidbcloud/devops/entity/privilege"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/jump/repo"
	"github.com/tidbcloud/devops/pkg/service/privilege/action"
	"github.com/tidbcloud/devops/pkg/service/privilege/api"
)

func (s *svc) RequestPrivilege(ctx context.Context, param api.RequestPrivilegeParam) (*uint64, error) {
	var id *uint64
	err := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		actor := action.NewPrivilegeActor(ctx, s.comps(), nil)
		err := actor.Request(param)
		if err != nil {
			return err
		}
		newPriv := actor.GetContext().Privilege

		id = &newPriv.ID
		return err
	})

	return id, err
}

func (s *svc) GetPrivilege(ctx context.Context, id uint64) (*privent.Privilege, error) {
	return s.repo.Get(ctx, id)
}

func (s *svc) ListPrivileges(ctx context.Context, param api.ListPrivilegesParam) ([]*privent.Privilege, int64, error) {
	optSet := s.repo.Options()
	opts := []repoopt.Option{}

	if param.PhaseIn != nil {
		opts = append(opts, optSet.PhaseIn(param.PhaseIn))
	}
	if param.PhaseNotIn != nil {
		opts = append(opts, optSet.PhaseNotIn(param.PhaseNotIn))
	}
	if param.BindingUserHas != nil {
		opts = append(opts, optSet.BindingUserHas(*param.BindingUserHas))
	}
	if param.SourceIs != nil {
		opts = append(opts, optSet.SourceIs(*param.SourceIs))
	}
	if param.SourceLike != nil {
		opts = append(opts, optSet.SourceLike(*param.SourceLike))
	}

	total, err := s.repo.Count(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	if param.Page > 0 {
		opts = append(opts, repo.AuthorizationOpts.WithPager(param.Page, param.PerPage))
	}

	privileges, err := s.repo.List(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	return privileges, total, nil
}

func (s *svc) RevokePrivilege(ctx context.Context, id uint64) error {
	priv, err := s.repo.Get(ctx, id)
	if err != nil {
		return err
	}

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		actor := action.NewPrivilegeActor(ctx, s.comps(), priv)
		return actor.Revoke()
	})
}

func (s *svc) UnbindingUsersOfPrivilege(ctx context.Context, id uint64, emails []string) ([]string, error) {
	priv, err := s.repo.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	removedEmails := []string{}
	err = s.dbp.Transaction(ctx, func(ctx context.Context) error {
		actor := action.NewPrivilegeActor(ctx, s.comps(), priv)
		removedEmails, err = actor.UnbindingUsers(emails)
		return err
	})

	return removedEmails, err
}

func (s *svc) WatchPrivileges(ctx context.Context) error {
	logger := log.FromContext(ctx)

	phaseToSync := []privent.PrivilegePhase{
		privent.PrivilegePhaseApproving,
	}
	privileges, err := s.repo.List(ctx, s.repo.Options().PhaseIn(phaseToSync))
	if err != nil {
		return err
	}

	if len(privileges) != 0 {
		logger.Info("watched some privileges ...",
			"privilege_ids", container.TransformSlice(privileges, func(i *privent.Privilege) uint64 { return i.ID }),
		)
	}

	for _, priv := range privileges {
		s.syncPrivilegeCh <- priv.ID
	}

	return nil
}

func (s *svc) WatchPrivilegesSlowly(ctx context.Context) error {
	logger := log.FromContext(ctx)

	phaseToSync := []privent.PrivilegePhase{
		privent.PrivilegePhaseActive,
	}
	privileges, err := s.repo.List(ctx, s.repo.Options().PhaseIn(phaseToSync))
	if err != nil {
		return err
	}

	if len(privileges) != 0 {
		logger.Info("watched some privileges slowly ...",
			"privilege_ids", container.TransformSlice(privileges, func(i *privent.Privilege) uint64 { return i.ID }),
		)
	}

	for _, priv := range privileges {
		s.syncPrivilegeCh <- priv.ID
	}

	return nil
}

func (s *svc) SyncPrivilege(ctx context.Context, id uint64) error {
	priv, err := s.repo.Get(ctx, id)
	if err != nil {
		return err
	}

	ctx = log.WrapContext(ctx, "phase", priv.Phase)

	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		actor := action.NewPrivilegeActor(ctx, s.comps(), priv)

		switch priv.Phase {
		case privent.PrivilegePhaseApproving:
			err = actor.GrantIfApproved()
		case privent.PrivilegePhaseActive:
			err = actor.ExpireIfNeeded()
		}

		return err
	})
}

func (s *svc) comps() action.Components {
	return action.Components{
		Repo:            s.repo,
		ApprovalService: s.apprSvc,
		UserService:     s.userSvc,
		LarkClient:      s.larkCli,
	}
}
