package serviceaccount

import (
	"context"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	serviceaccountstore "github.com/tidbcloud/AgentTeam/internal/store/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// ListManagersForUser returns the active explicit Human managers for one
// account only to workspace administrators or callers who explicitly manage
// that account. Workspace administrators stay implicit and are never
// synthesized into this authorization directory.
func (service *Service) ListManagersForUser(ctx context.Context, userID, workspaceID, accountID string) ([]serviceaccountstore.ManagerAssignment, error) {
	if err := service.validate(); err != nil {
		return nil, err
	}
	caller, err := service.loadWorkspaceHuman(ctx, userID, workspaceID)
	if err != nil {
		return nil, err
	}
	account, err := service.accounts.LoadAccount(ctx, workspaceID, accountID)
	if err != nil {
		return nil, err
	}
	if account.ManagementKind != serviceaccountstore.ManagementKindWorkspace {
		return nil, storeerr.Forbidden("workspace service account manager authority is required")
	}
	if !access.HasWorkspaceAdminRole(domain.WorkspaceRole(caller.WorkspaceRole)) {
		manager, err := service.accounts.HasActiveManager(ctx, workspaceID, accountID, caller.Member.ID)
		if err != nil {
			return nil, err
		}
		if !manager {
			return nil, storeerr.Forbidden("service account manager authority is required")
		}
	}
	return service.accounts.ListManagers(ctx, workspaceID, accountID, true)
}

// AddManagerForUser grants the account's single indivisible management and
// publishing authority to one active workspace Human. Workspace administrators
// already hold that authority implicitly and do not require an assignment.
func (service *Service) AddManagerForUser(ctx context.Context, userID, workspaceID, accountID, memberID string, now time.Time) (serviceaccountstore.ManagerAssignment, error) {
	if err := service.validate(); err != nil {
		return serviceaccountstore.ManagerAssignment{}, err
	}
	expectedCaller, err := service.loadWorkspaceHuman(ctx, userID, workspaceID)
	if err != nil {
		return serviceaccountstore.ManagerAssignment{}, err
	}
	managerID, err := identifiers.NewEntityID("samanager")
	if err != nil {
		return serviceaccountstore.ManagerAssignment{}, err
	}
	now = normalizedNow(now)
	var assignment serviceaccountstore.ManagerAssignment
	err = coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		caller, err := service.loadWorkspaceHumanForUpdate(ctx, tx, userID, workspaceID, expectedCaller.Member.ID)
		if err != nil {
			return err
		}
		accounts := service.accounts.InTx(tx)
		account, err := accounts.LoadAccountForUpdate(ctx, workspaceID, accountID)
		if err != nil {
			return err
		}
		if account.ManagementKind != serviceaccountstore.ManagementKindWorkspace {
			return storeerr.Forbidden("platform service account managers are controlled by platform operators")
		}
		if err := requireManagerMutationAuthority(ctx, accounts, caller, workspaceID, accountID); err != nil {
			return err
		}
		var upsertErr error
		assignment, upsertErr = accounts.UpsertManager(ctx, serviceaccountstore.ManagerUpsert{
			ID:                 managerID,
			WorkspaceID:        workspaceID,
			ServiceAccountID:   accountID,
			MemberID:           memberID,
			GrantedByMemberID:  caller.Member.ID,
			GrantedByActorType: "human",
			GrantedByActorID:   caller.Member.ID,
			Now:                now,
		})
		return upsertErr
	})
	return assignment, err
}

// RemoveManagerForUser ends one manager assignment. The store serializes all
// removals for the account and prevents it from losing its last manager.
func (service *Service) RemoveManagerForUser(ctx context.Context, userID, workspaceID, accountID, managerID string, expectedVersion int64, now time.Time) (serviceaccountstore.ManagerAssignment, error) {
	if err := service.validate(); err != nil {
		return serviceaccountstore.ManagerAssignment{}, err
	}
	expectedCaller, err := service.loadWorkspaceHuman(ctx, userID, workspaceID)
	if err != nil {
		return serviceaccountstore.ManagerAssignment{}, err
	}
	now = normalizedNow(now)
	var assignment serviceaccountstore.ManagerAssignment
	err = coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		caller, err := service.loadWorkspaceHumanForUpdate(ctx, tx, userID, workspaceID, expectedCaller.Member.ID)
		if err != nil {
			return err
		}
		accounts := service.accounts.InTx(tx)
		account, err := accounts.LoadAccountForUpdate(ctx, workspaceID, accountID)
		if err != nil {
			return err
		}
		if account.ManagementKind != serviceaccountstore.ManagementKindWorkspace {
			return storeerr.Forbidden("platform service account managers are controlled by platform operators")
		}
		if err := requireManagerMutationAuthority(ctx, accounts, caller, workspaceID, accountID); err != nil {
			return err
		}
		var endErr error
		assignment, endErr = accounts.EndManager(ctx, workspaceID, accountID, managerID, expectedVersion, now)
		return endErr
	})
	return assignment, err
}

func requireManagerMutationAuthority(ctx context.Context, accounts *serviceaccountstore.TxStore, caller access.HumanWorkspaceMember, workspaceID, accountID string) error {
	if access.HasWorkspaceAdminRole(domain.WorkspaceRole(caller.WorkspaceRole)) {
		return nil
	}
	manager, err := accounts.HasActiveManagerForUpdate(ctx, workspaceID, accountID, caller.Member.ID)
	if err != nil {
		return err
	}
	if !manager {
		return storeerr.Forbidden("service account manager authority is required")
	}
	return nil
}
