package roles

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/service/content"
	rolestore "github.com/tidbcloud/AgentTeam/internal/store/resource/roles"
	"gorm.io/gorm"
)

// RoleContentAccessPolicy rechecks live workspace membership and Role-domain
// grants at the Content Store boundary. A scoped principal is necessary but
// never sufficient authorization: request-local claims cannot outlive a
// membership or grant revocation.
type RoleContentAccessPolicy struct {
	workspace content.WorkspaceAccessVerifier
	roles     roleContentAccessStore
}

type roleContentAccessStore interface {
	GetRole(context.Context, string, string) (rolestore.Role, error)
	GetRoleGrant(context.Context, string, string, string, string) (rolestore.RoleGrant, error)
}

func NewRoleContentAccessPolicy(workspace content.WorkspaceAccessVerifier, roles roleContentAccessStore) (*RoleContentAccessPolicy, error) {
	if workspace == nil || roles == nil {
		return nil, fmt.Errorf("role content workspace verifier and role store are required")
	}
	return &RoleContentAccessPolicy{workspace: workspace, roles: roles}, nil
}

func (policy *RoleContentAccessPolicy) AuthorizeContentAccess(ctx context.Context, request content.AccessRequest) error {
	if policy == nil || policy.workspace == nil || policy.roles == nil || strings.TrimSpace(request.Domain) != rolePackageContentDomain {
		return content.ErrAccessDenied
	}
	if err := (content.ScopedAccessPolicy{}).AuthorizeContentAccess(ctx, request); err != nil {
		return err
	}
	workspaceID := strings.TrimSpace(request.WorkspaceID)
	roleID := strings.TrimSpace(request.BindingKey)
	subject := strings.TrimSpace(request.Principal.Subject)
	allowed, err := policy.workspace.HasWorkspaceAccess(ctx, workspaceID, subject)
	if err != nil {
		return fmt.Errorf("verify role content workspace access: %w", err)
	}
	if !allowed {
		return content.ErrAccessDenied
	}
	role, err := policy.roles.GetRole(ctx, workspaceID, roleID)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return content.ErrAccessDenied
	}
	if err != nil {
		return fmt.Errorf("load role content owner: %w", err)
	}
	grant := ""
	if strings.TrimSpace(role.CreatedBy) == subject {
		grant = roleGrantOwner
	} else if row, grantErr := policy.roles.GetRoleGrant(ctx, workspaceID, roleID, "user", subject); grantErr == nil {
		grant = normalizeRoleGrant(row.Role)
	} else if !errors.Is(grantErr, gorm.ErrRecordNotFound) {
		return fmt.Errorf("load role content grant: %w", grantErr)
	} else if roleVisibilityForRead(role.Visibility) == RoleVisibilityWorkspace {
		grant = roleGrantView
	}
	if !roleContentGrantAllows(grant, request.Action) {
		return content.ErrAccessDenied
	}
	return nil
}

func roleContentGrantAllows(grant string, action content.AccessAction) bool {
	switch action {
	case content.AccessReadMetadata, content.AccessReadPayload,
		content.AccessReadHistory, content.AccessCompare,
		content.AccessReadArchive:
		return normalizeRoleGrant(grant) != ""
	case content.AccessCreateLineage, content.AccessPublish,
		content.AccessManagePin:
		return roleCanManage(grant)
	default:
		return false
	}
}

var _ content.ContentAccessPolicy = (*RoleContentAccessPolicy)(nil)
