// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.

package accesspolicy

import (
	"context"

	"github.com/cosi-project/runtime/pkg/resource"
	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/cosi-project/runtime/pkg/state"

	authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
	"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
	"github.com/siderolabs/omni/internal/pkg/auth"
	"github.com/siderolabs/omni/internal/pkg/auth/actor"
	"github.com/siderolabs/omni/internal/pkg/auth/role"
	"github.com/siderolabs/omni/internal/pkg/ctxstore"
)

// RoleForCluster returns the role of the current user for the given cluster, and whether the role matches all clusters.
func RoleForCluster(ctx context.Context, id resource.ID, st state.State) (role.Role, bool, error) {
	userRole := role.None

	if val, ok := ctxstore.Value[auth.RoleContextKey](ctx); ok {
		userRole = val.Role
	}

	ctx = actor.MarkContextAsInternalActor(ctx)

	accessPolicy, err := safe.StateGet[*authres.AccessPolicy](ctx, st, authres.NewAccessPolicy().Metadata())
	if err != nil {
		if state.IsNotFoundError(err) {
			return userRole, false, nil
		}

		return role.None, false, err
	}

	identityVal, identityExists := ctxstore.Value[auth.IdentityContextKey](ctx)
	if !identityExists {
		return userRole, false, nil
	}

	identity, err := safe.StateGet[*authres.Identity](ctx, st, authres.NewIdentity(identityVal.Identity).Metadata())
	if err != nil {
		if state.IsNotFoundError(err) {
			return userRole, false, nil
		}

		return role.None, false, err
	}

	clusterMD := omni.NewCluster(id).Metadata()

	checkResult, err := Check(accessPolicy, clusterMD, identity.Metadata())
	if err != nil {
		return role.None, false, err
	}

	maxRole, err := role.Max(userRole, checkResult.Role)
	if err != nil {
		return role.None, false, err
	}

	return maxRole, checkResult.MatchesAllClusters, nil
}

// ApplyClusterAccessPolicy checks the ACLs for the user in the context against the given cluster ID.
// If there is a match and the matched role is higher than the user's role,
// a child context containing the given role will be returned.
func ApplyClusterAccessPolicy(ctx context.Context, clusterName string, st state.State) (context.Context, error) {
	clusterRole, _, err := RoleForCluster(ctx, clusterName, st)
	if err != nil {
		return nil, err
	}

	userRole := role.None

	if val, ok := ctxstore.Value[auth.RoleContextKey](ctx); ok {
		userRole = val.Role
	}

	newRole, err := role.Max(userRole, clusterRole)
	if err != nil {
		return nil, err
	}

	if newRole == userRole {
		return ctx, nil
	}

	return ctxstore.WithValue(ctx, auth.RoleContextKey{Role: newRole}), nil
}
