using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AutopilotMonitor.Shared.DataAccess
{
///
/// Repository for admin and tenant member management.
/// Covers: GlobalAdmins, TenantAdmins, McpUsers, DelegatedAdmins tables.
///
public interface IAdminRepository
{
// --- Global Admins ---
Task IsGlobalAdminAsync(string upn);
///
/// Resolves the platform role for a UPN: "GlobalAdmin", "GlobalReader", or null when there is no
/// enabled GlobalAdmins row. Empty/missing Role on an existing enabled row ⇒ "GlobalAdmin" (back-compat).
///
Task GetGlobalRoleAsync(string upn);
Task> GetAllGlobalAdminsAsync();
Task AddGlobalAdminAsync(string upn, string addedBy);
Task RemoveGlobalAdminAsync(string upn);
Task DisableGlobalAdminAsync(string upn);
// --- MCP Users ---
Task IsMcpUserAsync(string upn);
Task GetMcpUserAsync(string upn);
Task> GetAllMcpUsersAsync();
Task AddMcpUserAsync(string upn, string addedBy);
Task RemoveMcpUserAsync(string upn);
Task SetMcpUserEnabledAsync(string upn, bool isEnabled);
Task SetMcpUserUsagePlanAsync(string upn, string? usagePlan);
// --- Delegated Admins (scoped-global / "MSP mode") ---
/// Every assignment row across all delegated admins (full-table scan — the management UI lists
/// all grants; the table holds only admin assignments, so this stays small). Not a hot path.
Task> GetAllDelegatedAdminsAsync();
/// All assignment rows for one delegated-admin UPN (PK scan), regardless of status/enabled.
/// Callers that need only effective scope filter on Status==Active && IsEnabled.
Task> GetDelegatedTenantsAsync(string upn);
/// All assignment rows targeting one tenant (cross-partition RowKey scan — admin UI, not hot path).
/// Powers the customer's "who manages my tenant?" view including pending requests.
Task> GetDelegatedAssigneesAsync(string tenantId);
Task GetDelegatedAdminAsync(string upn, string tenantId);
/// Creates or replaces an assignment row (upsert on PK=upn, RK=tenantId).
Task UpsertDelegatedAdminAsync(string upn, string tenantId, string role, string status, string source, string grantedBy);
Task SetDelegatedAdminStatusAsync(string upn, string tenantId, string status);
Task SetDelegatedAdminEnabledAsync(string upn, string tenantId, bool isEnabled);
Task RemoveDelegatedAdminAsync(string upn, string tenantId);
// --- Tenant Groups (app-internal tenant bundles for delegated admins / "MSP mode") ---
/// Creates a group (meta row) and returns the generated groupId.
Task CreateTenantGroupAsync(string name, string createdBy);
///
/// Updates a group's meta row: and are each left
/// unchanged when null; an empty label clears it. Returns false if the group does not exist.
///
Task UpdateTenantGroupAsync(string groupId, string? name, string? customerLabel);
/// Deletes a group: all rows in its partition (meta + membership) AND every UPN
/// assignment referencing it (cross-partition RowKey scan of the assignments table).
Task DeleteTenantGroupAsync(string groupId);
/// All groups with their tenant members + assignee counts — for the management UI (not hot path).
Task> GetAllTenantGroupsAsync();
Task GetTenantGroupAsync(string groupId);
/// Creates the meta row of a tenant-OWNED (self-service) group if missing — idempotent, never touches an existing group.
Task EnsureOwnedTenantGroupAsync(string groupId, string name, string ownerTenantId);
/// Ids of every group partition holding a membership row for the tenant (cross-partition RowKey scan; existence is re-checked meta-backed by callers).
Task> GetGroupIdsContainingTenantAsync(string tenantId);
Task AddTenantToGroupAsync(string groupId, string tenantId);
Task RemoveTenantFromGroupAsync(string groupId, string tenantId);
/// Tenant IDs (lowercase) in a group, excluding the meta row (HOT PATH — PartitionKey scan).
Task> GetGroupTenantsAsync(string groupId);
/// The group's tenant members PLUS its meta-row flags from the same single partition read
/// (HOT PATH — scope resolution). Null when the group does not exist (no meta row).
Task GetGroupMembershipAsync(string groupId);
/// Creates or replaces a UPN→group assignment.
Task AssignGroupAsync(string upn, string groupId, string role, bool isEnabled, string assignedBy);
Task UnassignGroupAsync(string upn, string groupId);
/// All group assignments for one UPN (HOT PATH — PartitionKey point-scan; scope resolution).
Task> GetGroupAssignmentsForUpnAsync(string upn);
/// All UPNs assigned to one group (cross-partition RowKey scan — management UI, not hot path).
Task> GetGroupAssigneesAsync(string groupId);
// --- Admin identity bindings (tid + oid behind every cross-tenant-role UPN) ---
/// The binding for a UPN, or null when none exists (⇒ no cross-tenant role resolves).
Task GetIdentityBindingAsync(string upn);
/// Every binding — for the operator management UI (small table, not a hot path).
Task> GetAllIdentityBindingsAsync();
/// Every binding homed in one tenant (single-partition property filter) — the delegated slot count's attribution.
Task> GetIdentityBindingsByHomeTenantAsync(string homeTenantId);
/// Creates or replaces the binding for a UPN. null/empty ⇒ pinned on first sign-in.
Task UpsertIdentityBindingAsync(string upn, string tenantId, string? objectId, string boundBy);
///
/// Pins onto an existing binding ONLY IF the binding is homed in
/// and has no object id yet (optimistic-concurrency guarded). Returns the
/// binding as stored after the attempt (null when no binding exists) so the caller can re-verify.
///
Task TryPinIdentityObjectIdAsync(string upn, string tenantId, string objectId);
Task RemoveIdentityBindingAsync(string upn);
// --- Tenant Members ---
Task> GetTenantMembersAsync(string tenantId);
Task AddTenantMemberAsync(string tenantId, string upn, string addedBy, string role, bool canManageBootstrapTokens = false);
Task RemoveTenantMemberAsync(string tenantId, string upn);
Task UpdateMemberPermissionsAsync(string tenantId, string upn, string role, bool canManageBootstrapTokens);
Task SetTenantMemberEnabledAsync(string tenantId, string upn, bool isEnabled);
Task GetTenantMemberAsync(string tenantId, string upn);
Task IsTenantAdminAsync(string tenantId, string upn);
}
public class GlobalAdminEntry
{
public string Upn { get; set; } = string.Empty;
public bool IsEnabled { get; set; } = true;
public DateTime AddedAt { get; set; }
public string AddedBy { get; set; } = string.Empty;
/// Platform role: "GlobalAdmin" (default) or "GlobalReader". Empty ⇒ GlobalAdmin (back-compat).
public string Role { get; set; } = string.Empty;
}
///
/// One delegated-admin assignment: UPN X may access tenant Y at role Role. The "scoped global" tier
/// (subset of tenants) between a single-tenant member and a platform GlobalAdmin. Surfaced as "MSP mode".
///
public class DelegatedAdminEntry
{
public string Upn { get; set; } = string.Empty;
public string TenantId { get; set; } = string.Empty;
/// Constants.DelegatedRoles: "DelegatedReader" (default) or "DelegatedAdmin".
public string Role { get; set; } = string.Empty;
public bool IsEnabled { get; set; } = true;
/// Constants.DelegatedStatus: "Active" / "PendingApproval" / "Revoked". Only Active confers scope.
public string Status { get; set; } = string.Empty;
/// Constants.DelegatedSource: "OperatorGranted" / "CustomerDelegated".
public string Source { get; set; } = string.Empty;
public DateTime GrantedAt { get; set; }
public string GrantedBy { get; set; } = string.Empty;
}
///
/// A Tenant Group: an app-internal named bundle of tenants. A delegated admin assigned to the
/// group (see ) gains read scope to every tenant in
/// . Adding a tenant to the group grants it to all assignees at once.
///
public class TenantGroup
{
public string GroupId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string CreatedBy { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
/// Tenant IDs (lowercase) in this group.
public List TenantIds { get; set; } = new();
/// Number of UPNs assigned to this group (== .Count).
public int AssigneeCount { get; set; }
/// The UPNs assigned to this group (for the management UI).
public List Assignees { get; set; } = new();
/// The managing tenant that owns this self-service group (msp-{tenantId}); null for operator-created groups.
public string? OwnerTenantId { get; set; }
/// Operator-created groups only: the name a managed customer sees for this access instead of the neutral "Platform support"; absent when none is set. The group name itself never reaches a customer.
public string? CustomerLabel { get; set; }
}
///
/// Hot-path projection of one Tenant Group: its tenant members and the meta-row flags that shape the
/// scope they confer, read from the single group partition (see ).
///
public class TenantGroupMembership
{
public string GroupId { get; set; } = string.Empty;
/// Tenant IDs (lowercase) in this group.
public List TenantIds { get; set; } = new();
}
///
/// The immutable Entra identity behind a cross-tenant-role UPN: the HOME tenant the UPN was granted for
/// and, once known, the user's object id. A role row (GlobalAdmins / DelegatedAdmins / TenantGroupAssignments)
/// confers nothing unless the caller's validated JWT matches this binding.
///
public class AdminIdentityBinding
{
public string Upn { get; set; } = string.Empty;
/// The admin's home Entra tenant id (lowercase GUID) — must equal the JWT tid.
public string TenantId { get; set; } = string.Empty;
/// The admin's Entra object id (lowercase GUID) — must equal the JWT oid. Empty ⇒ pinned on first sign-in.
public string ObjectId { get; set; } = string.Empty;
public string BoundBy { get; set; } = string.Empty;
public DateTime BoundAt { get; set; }
/// When the object id was pinned (grant time, or the first matching sign-in). Null while unpinned.
public DateTime? ObjectIdPinnedAt { get; set; }
public bool IsObjectIdPinned => !string.IsNullOrEmpty(ObjectId);
}
/// One UPN→group assignment. PK=UPN, RK=groupId in storage.
public class TenantGroupAssignment
{
public string Upn { get; set; } = string.Empty;
public string GroupId { get; set; } = string.Empty;
/// Constants.DelegatedRoles: "DelegatedReader" (default) or "DelegatedAdmin".
public string Role { get; set; } = string.Empty;
public bool IsEnabled { get; set; } = true;
public string AssignedBy { get; set; } = string.Empty;
public DateTime AssignedAt { get; set; }
}
public class McpUserEntry
{
public string Upn { get; set; } = string.Empty;
public bool IsEnabled { get; set; } = true;
public DateTime AddedAt { get; set; }
public string AddedBy { get; set; } = string.Empty;
public string? UsagePlan { get; set; }
}
public class TenantMember
{
public string Upn { get; set; } = string.Empty;
public string TenantId { get; set; } = string.Empty;
public string Role { get; set; } = "Admin";
public bool IsEnabled { get; set; } = true;
public bool CanManageBootstrapTokens { get; set; }
public DateTime AddedAt { get; set; }
public string AddedBy { get; set; } = string.Empty;
}
}