package action

import (
	"fmt"
	"slices"
	"time"

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

type actorApproving struct {
	*baseActor
}

func (a *actorApproving) GrantIfApproved() error {
	ctx := a.Context
	priv := a.Privilege

	if priv.ApprovalID == 0 {
		return fmt.Errorf("phase is approving but approval id is nil")
	}

	appr, err := a.ApprovalService.GetApproval(ctx, priv.ApprovalID)
	if err != nil {
		return fmt.Errorf("get approval '%d' failed: %s", priv.ApprovalID, err)
	}

	switch appr.Status {
	case apprent.ApprovalStatusRejected, apprent.ApprovalStatusCancelled:
		priv.Phase = privent.PrivilegePhaseUnapproved
		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

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

		priv.Phase = privent.PrivilegePhaseActive
		_, err = a.Repo.Update(ctx, priv)
		if err != nil {
			return fmt.Errorf("update privilege failed: %w", err)
		}

		return nil
	}

	return errs.RetriableErrorf("approval '%d' is still %s", priv.ApprovalID, appr.Status)
}

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

	if priv.ApprovalID != 0 {
		err := a.ApprovalService.CancelApproval(ctx, priv.ApprovalID)
		if err != nil {
			return 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 *actorApproving) 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)
	}

	return removedEmails, nil
}

func (a *actorApproving) grant() error {
	ctx := a.Context
	priv := a.Privilege

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

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

	return nil
}

func privilegeToLarkParam(priv *privent.Privilege) larknotify.PrivilegeParam {
	return larknotify.PrivilegeParam{
		ID:         priv.ID,
		Type:       priv.Type,
		Content:    priv.Content,
		ExpireTime: time.Unix(priv.ExpiredAt, 0),
		DetailLink: DetailLink(priv.ID),
	}
}

func DetailLink(privID uint64) string {
	return fmt.Sprintf("https://%s/privilege/%d", consts.ConsoleAddr(), privID)
}
