package action

import (
	"fmt"
	"slices"
	"time"

	privent "github.com/tidbcloud/devops/entity/privilege"
	larknotify "github.com/tidbcloud/devops/pkg/infra/clients/larkcli/notify"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
)

type actorActive struct {
	*baseActor
}

func (a *actorActive) ExpireIfNeeded() error {
	ctx := a.Context
	priv := a.Privilege

	now := time.Now()
	expireTime := time.Unix(priv.ExpiredAt, 0)
	if now.After(expireTime) {
		err := a.revoke()
		if err != nil {
			return fmt.Errorf("revoke privilege failed: %w", err)
		}

		priv.Phase = privent.PrivilegePhaseRevoked
		priv.ClosedAt = pointer.Pointer(time.Now().Unix())
		_, err = a.Repo.Update(ctx, priv)
		if err != nil {
			return fmt.Errorf("update privilege failed: %w", err)
		}

		return nil
	}

	return errs.RetriableErrorf("privilege is still active")
}

func (a *actorActive) Revoke() error {
	ctx := a.Context
	priv := a.Privilege

	err := a.revoke()
	if err != nil {
		return fmt.Errorf("revoke privilege failed: %w", err)
	}

	priv.Phase = privent.PrivilegePhaseRevoked
	priv.ClosedAt = pointer.Pointer(time.Now().Unix())
	_, err = a.Repo.Update(ctx, priv)
	if err != nil {
		return fmt.Errorf("update privilege failed: %w", err)
	}

	return nil
}

func (a *actorActive) UnbindingUsers(emails []string) ([]string, error) {
	ctx := a.Context
	priv := a.Privilege

	// unbinding user
	removedEmails := []string{}
	priv.Bindings = slices.DeleteFunc(priv.Bindings, func(bu *privent.PrivilegeBinding) bool {
		ok := slices.Contains(emails, bu.User)
		if ok {
			removedEmails = append(removedEmails, bu.User)
		}
		return ok
	})
	_, err := a.Repo.Update(ctx, priv)
	if err != nil {
		return nil, fmt.Errorf("update privilege failed: %w", err)
	}

	// notify
	err = a.notifyUsers(larknotify.PrivilegeRevokedParam{
		PrivilegeParam: privilegeToLarkParam(priv),
	}, removedEmails)
	if err != nil {
		// only log the error
		log.FromContext(ctx).Error(err, "failed to notify some users for privilege revoked", "emails", removedEmails)
	}

	return removedEmails, nil
}

func (a *actorActive) revoke() error {
	ctx := a.Context
	priv := a.Privilege

	// revoke
	switch priv.Type {
	case privent.PrivilegeTypeApproval:
		// For approval privileges, no need to do anything
	default:
		return fmt.Errorf("unknown privilege type '%s'", priv.Type)
	}

	// notify
	err := a.notifyUsers(larknotify.PrivilegeRevokedParam{
		PrivilegeParam: privilegeToLarkParam(priv),
	}, nil)
	if err != nil {
		// only log the error
		log.FromContext(ctx).Error(err, "failed to notify some users for privilege revoked")
	}

	return nil
}
