@page "/portal"
@using EntKube.Web.Data
@using EntKube.Web.Services
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@attribute [Authorize]
@rendermode InteractiveServer
@inject CustomerAccessService CustomerAccessService
@inject DeploymentService DeploymentService
@inject TenantService TenantService
@inject KubernetesOperationsService K8sOps
@inject KeycloakService KeycloakService
@inject HarborService HarborService
@inject VaultService VaultService
@inject PrometheusService PrometheusService
@inject AuditService AuditService
@inject IncidentService IncidentService
@inject CustomerGitService CustomerGitService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Navigation

<PageTitle>Customer Portal</PageTitle>

@* ═══════════════════════════════════════════════════════════════════
   Customer Portal — a scoped view where a user sees only the customers
   they've been granted access to. From here they can drill into apps,
   deployments, view pod logs, restart deployments, and delete pods.

   Navigation flow: My Customers → Apps → Deployments → Operations
   ═══════════════════════════════════════════════════════════════════ *@

@if (loading)
{
    <div class="text-center py-5">
        <div class="spinner-border text-primary" role="status"></div>
        <p class="text-muted mt-2">Loading portal...</p>
    </div>
}
else if (customers is null || customers.Count == 0)
{
    <EmptyState Icon="bi-shield-lock"
                Title="No access granted"
                Message="You don't have access to any customers yet. Ask a tenant administrator to grant you access." />
}
else
{
    @* ── Level 0: Breadcrumb navigation ── *@
    <nav aria-label="breadcrumb" class="mb-3">
        <ol class="breadcrumb small">
            <li class="breadcrumb-item">
                @if (selectedDeployment is not null || selectedIdentityRealm is not null || selectedSecretsApp is not null || selectedRegistryProject is not null || selectedGovernanceApp is not null || selectedResourcesApp is not null || showGitPolicies)
                {
                    <a href="javascript:void(0)" class="text-decoration-none" @onclick="BackToApps">
                        <i class="bi bi-grid me-1"></i>Portal
                    </a>
                }
                else if (selectedCustomer is not null)
                {
                    <a href="javascript:void(0)" class="text-decoration-none" @onclick="BackToCustomers">
                        <i class="bi bi-grid me-1"></i>Portal
                    </a>
                }
                else
                {
                    <span class="text-muted"><i class="bi bi-grid me-1"></i>Portal</span>
                }
            </li>

            @if (selectedCustomer is not null)
            {
                <li class="breadcrumb-item">
                    @if (selectedDeployment is not null || selectedIdentityRealm is not null || selectedSecretsApp is not null || selectedRegistryProject is not null || selectedGovernanceApp is not null || selectedResourcesApp is not null || showGitPolicies)
                    {
                        <a href="javascript:void(0)" class="text-decoration-none" @onclick="BackToApps">@selectedCustomer.Name</a>
                    }
                    else
                    {
                        <span class="text-muted">@selectedCustomer.Name</span>
                    }
                </li>
            }

            @if (selectedDeployment is not null)
            {
                <li class="breadcrumb-item active">@selectedDeployment.Name</li>
            }
            else if (selectedIdentityRealm is not null)
            {
                <li class="breadcrumb-item active">
                    <i class="bi bi-shield-lock me-1"></i>@selectedIdentityRealm.DisplayName
                </li>
            }
            else if (selectedSecretsApp is not null)
            {
                <li class="breadcrumb-item active">
                    <i class="bi bi-shield-lock me-1"></i>@selectedSecretsApp.Name — Secrets
                </li>
            }
            else if (selectedRegistryProject is not null)
            {
                <li class="breadcrumb-item active">
                    <i class="bi bi-archive me-1"></i>@selectedRegistryProject.ProjectName — Registry
                </li>
            }
            else if (selectedGovernanceApp is not null)
            {
                <li class="breadcrumb-item active">
                    <i class="bi bi-shield-check me-1"></i>@selectedGovernanceApp.Name — Policy
                </li>
            }
            else if (selectedResourcesApp is not null)
            {
                <li class="breadcrumb-item active">
                    <i class="bi bi-diagram-3 me-1"></i>@selectedResourcesApp.Name — Resources
                </li>
            }
            else if (showGitPolicies)
            {
                <li class="breadcrumb-item active">
                    <i class="bi bi-git me-1"></i>Git Policies
                </li>
            }
        </ol>
    </nav>

    @* ════════════════════════════════════════════════════════════════
       Level 1: Customer list
       ════════════════════════════════════════════════════════════════ *@
    @if (selectedCustomer is null)
    {
        <div class="d-flex align-items-center mb-4">
            <i class="bi bi-grid fs-3 me-2 text-primary"></i>
            <h2 class="mb-0">My Customers</h2>
        </div>

        <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
            @foreach (Customer customer in customers)
            {
                <div class="col">
                    <div class="card shadow-sm h-100" role="button" style="cursor: pointer;"
                         @onclick="() => SelectCustomer(customer)">
                        <div class="card-body">
                            <div class="d-flex align-items-center mb-2">
                                <i class="bi bi-people fs-4 me-2 text-primary"></i>
                                <h5 class="mb-0">@customer.Name</h5>
                            </div>
                            <div class="text-muted small">
                                <i class="bi bi-app-indicator me-1"></i>
                                @customer.Apps.Count app@(customer.Apps.Count != 1 ? "s" : "")
                            </div>
                        </div>
                    </div>
                </div>
            }
        </div>
    }

    @* ════════════════════════════════════════════════════════════════
       Level 3: Git URL policies (read-only)
       ════════════════════════════════════════════════════════════════ *@
    else if (showGitPolicies)
    {
        <div class="card shadow-sm">
            <div class="card-header bg-white py-2 d-flex align-items-center gap-2">
                <span class="bi bi-git text-primary"></span>
                <strong>Git URL Policies — @selectedCustomer!.Name</strong>
                <button class="btn-close btn-sm ms-auto" @onclick="() => showGitPolicies = false"></button>
            </div>
            <div class="card-body">
                <p class="text-muted small mb-3">
                    These URL patterns define which git repositories you may use in deployments.
                    Contact your administrator to change them.
                </p>
                @if (gitPolicies.Count == 0)
                {
                    <div class="text-muted small">No git URL policies defined.</div>
                }
                else
                {
                    <ul class="list-group list-group-flush">
                        @foreach (CustomerGitRepoPolicy policy in gitPolicies)
                        {
                            <li class="list-group-item d-flex align-items-center gap-2 px-0">
                                <i class="bi bi-check-circle text-success"></i>
                                <code class="small">@policy.UrlPattern</code>
                            </li>
                        }
                    </ul>
                }
            </div>
        </div>
    }

    @* ════════════════════════════════════════════════════════════════
       Level 2: Apps and deployments for a selected customer
       ════════════════════════════════════════════════════════════════ *@
    else if (selectedDeployment is null && selectedIdentityRealm is null && selectedSecretsApp is null && selectedRegistryProject is null && !showGitPolicies)
    {
        <div class="d-flex align-items-center mb-3">
            <i class="bi bi-people fs-3 me-2 text-primary"></i>
            <div>
                <h2 class="mb-0">@selectedCustomer.Name</h2>
                <small class="text-muted">@selectedCustomer.Apps.Count app@(selectedCustomer.Apps.Count != 1 ? "s" : "")</small>
            </div>
            <div class="ms-auto d-flex gap-2">
                <button class="btn btn-sm @(showMonitoringPanel ? "btn-primary" : "btn-outline-secondary")"
                        @onclick="() => showMonitoringPanel = !showMonitoringPanel">
                    <span class="bi bi-heart-pulse me-1"></span>Monitoring
                </button>
                @if (gitPolicies.Count > 0)
                {
                    <button class="btn btn-sm btn-outline-secondary" @onclick="() => showGitPolicies = true" title="Git URL Policies">
                        <span class="bi bi-git me-1"></span>Git Policies
                    </button>
                }
                <a class="btn btn-sm btn-outline-secondary" href="/portal/status">
                    <span class="bi bi-activity me-1"></span>Status Overview
                </a>
            </div>
        </div>

        @* ── Maintenance notice ── *@
        <CustomerMaintenanceNotice TenantId="selectedCustomer.TenantId" ClusterIds="AllCustomerClusterIds" />

        @* ── Monitoring panel (toggled) ── *@
        @if (showMonitoringPanel)
        {
            <div class="card shadow-sm mb-4">
                <div class="card-header bg-white d-flex align-items-center gap-2 py-2">
                    <span class="bi bi-heart-pulse text-primary"></span>
                    <strong>Monitoring — @selectedCustomer.Name</strong>
                    <button class="btn-close btn-sm ms-auto" @onclick="() => showMonitoringPanel = false"></button>
                </div>
                <div class="card-body p-0">
                    <div class="p-3 border-bottom">
                        <h6 class="text-muted small mb-2"><span class="bi bi-shield-check me-1"></span>SLA & Uptime</h6>
                        @if (AllCustomerDeployments.Count > 0)
                        {
                            <CustomerSlaReport
                                Deployments="AllCustomerDeployments"
                                CustomerId="selectedCustomer.Id"
                                TenantId="selectedCustomer.TenantId" />
                        }
                        else { <div class="text-muted small">No deployments yet.</div> }
                    </div>
                    <div class="p-3">
                        <h6 class="text-muted small mb-2"><span class="bi bi-clock-history me-1"></span>Incident History (30 days)</h6>
                        @if (AllCustomerDeployments.Count > 0)
                        {
                            <CustomerIncidentHistory Deployments="AllCustomerDeployments" WindowDays="30" />
                        }
                        else { <div class="text-muted small">No deployments yet.</div> }
                    </div>
                </div>
            </div>
        }

        @if (selectedCustomer.Apps.Count == 0)
        {
            <EmptyState Icon="bi-app-indicator"
                        Message="No apps configured for this customer yet." />
        }
        else
        {
            @foreach (Data.App app in selectedCustomer.Apps.OrderBy(a => a.Name))
            {
                bool hasIdentity = appRealms.ContainsKey(app.Id);
                bool hasRegistry = appHarborProjects.ContainsKey(app.Id);
                bool showingCreateForm = createDeployAppId == app.Id;
                <div class="card shadow-sm mb-3">
                    <div class="card-header bg-white py-2">
                        <div class="d-flex align-items-center justify-content-between">
                            <div class="d-flex align-items-center">
                                <i class="bi bi-app-indicator me-2 text-primary"></i>
                                <strong>@app.Name</strong>
                            </div>
                            <div class="d-flex gap-2 align-items-center">
                                @if (currentAccessRole >= CustomerAccessRole.Admin)
                                {
                                    <button class="btn btn-sm btn-outline-primary"
                                            @onclick="() => ToggleCreateDeployment(app.Id)">
                                        <i class="bi bi-plus me-1"></i>New Deployment
                                    </button>
                                }
                                <button class="btn btn-sm btn-outline-secondary"
                                        @onclick="() => SelectSecretsApp(app)"
                                        title="Secrets, databases, messaging & cache">
                                    <i class="bi bi-key me-1"></i>Secrets
                                </button>
                                <div class="dropdown">
                                    <button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
                                            data-bs-toggle="dropdown" aria-expanded="false">
                                        <i class="bi bi-three-dots me-1"></i>More
                                    </button>
                                    <ul class="dropdown-menu dropdown-menu-end">
                                        @if (hasIdentity)
                                        {
                                            <li>
                                                <button class="dropdown-item" @onclick="() => SelectIdentityRealm(app.Id)">
                                                    <i class="bi bi-shield-lock me-2 text-primary"></i>Identity
                                                </button>
                                            </li>
                                        }
                                        @if (hasRegistry)
                                        {
                                            <li>
                                                <button class="dropdown-item" @onclick="() => SelectRegistryProject(app.Id)">
                                                    <i class="bi bi-archive me-2 text-primary"></i>Registry
                                                </button>
                                            </li>
                                        }
                                        <li>
                                            <button class="dropdown-item" @onclick="() => SelectGovernanceApp(app)">
                                                <i class="bi bi-shield-check me-2"></i>Policy
                                            </button>
                                        </li>
                                        <li>
                                            <button class="dropdown-item" @onclick="() => SelectResourcesApp(app)">
                                                <i class="bi bi-diagram-3 me-2"></i>Resources
                                            </button>
                                        </li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="card-body">
                        @if (showingCreateForm)
                        {
                            <div class="card border-primary mb-3">
                                <div class="card-body">
                                    <h6 class="mb-3"><i class="bi bi-plus-circle me-1"></i>Create Deployment</h6>
                                    @if (createDeployError is not null)
                                    {
                                        <div class="alert alert-danger py-1 small mb-2">@createDeployError</div>
                                    }
                                    <div class="row g-2 mb-2">
                                        <div class="col-md-6">
                                            <label class="form-label small">Name</label>
                                            <input class="form-control form-control-sm" @bind="newDeployName" placeholder="e.g. my-app-prod" />
                                        </div>
                                        <div class="col-md-6">
                                            <label class="form-label small">Type</label>
                                            <select class="form-select form-select-sm" @bind="newDeployType">
                                                <option value="@DeploymentType.Manual">Manual</option>
                                                <option value="@DeploymentType.Yaml">YAML</option>
                                                <option value="@DeploymentType.HelmChart">Helm Chart</option>
                                                @if (gitPolicies.Count > 0)
                                                {
                                                    <option value="@DeploymentType.GitYaml">Git — YAML</option>
                                                    <option value="@DeploymentType.GitHelm">Git — Helm</option>
                                                    <option value="@DeploymentType.GitAppOfApps">Git — App of Apps</option>
                                                }
                                            </select>
                                        </div>
                                        <div class="col-md-4">
                                            <label class="form-label small">Environment</label>
                                            <select class="form-select form-select-sm" @bind="newDeployEnvId">
                                                <option value="@Guid.Empty">Select...</option>
                                                @foreach (Data.Environment env in tenantEnvironments)
                                                {
                                                    <option value="@env.Id">@env.Name</option>
                                                }
                                            </select>
                                        </div>
                                        <div class="col-md-4">
                                            <label class="form-label small">Cluster</label>
                                            <select class="form-select form-select-sm" @bind="newDeployClusterId">
                                                <option value="@Guid.Empty">Select...</option>
                                                @foreach (KubernetesCluster cluster in tenantClusters.Where(c => newDeployEnvId == Guid.Empty || c.EnvironmentId == newDeployEnvId))
                                                {
                                                    <option value="@cluster.Id">@cluster.Name</option>
                                                }
                                            </select>
                                        </div>
                                        <div class="col-md-4">
                                            <label class="form-label small">Namespace</label>
                                            @if (!string.IsNullOrWhiteSpace(app.Namespace))
                                            {
                                                <input class="form-control form-control-sm font-monospace" value="@app.Namespace" disabled />
                                                <div class="form-text small text-warning"><i class="bi bi-lock me-1"></i>Locked by governance policy.</div>
                                            }
                                            else
                                            {
                                                <input class="form-control form-control-sm" @bind="newDeployNamespace" placeholder="e.g. my-app" />
                                            }
                                        </div>
                                        @if (newDeployType == DeploymentType.HelmChart)
                                        {
                                            <div class="col-md-4">
                                                <label class="form-label small">Helm Repo URL</label>
                                                <input class="form-control form-control-sm" @bind="newHelmRepoUrl" placeholder="https://charts.example.com" />
                                            </div>
                                            <div class="col-md-4">
                                                <label class="form-label small">Chart Name</label>
                                                <input class="form-control form-control-sm" @bind="newHelmChartName" />
                                            </div>
                                            <div class="col-md-4">
                                                <label class="form-label small">Chart Version</label>
                                                <input class="form-control form-control-sm" @bind="newHelmChartVersion" placeholder="e.g. 1.0.0" />
                                            </div>
                                        }
                                        @if (newDeployType is DeploymentType.GitYaml or DeploymentType.GitHelm or DeploymentType.GitAppOfApps)
                                        {
                                            <div class="col-12">
                                                @if (customerGitCredentials.Count > 0)
                                                {
                                                    <div class="alert alert-info py-2 small mb-2">
                                                        <i class="bi bi-key-fill me-1"></i>
                                                        <strong>@customerGitCredentials.Count stored credential@(customerGitCredentials.Count != 1 ? "s" : "") on file</strong>
                                                        (@string.Join(", ", customerGitCredentials.Select(c => c.Name))) — no need to enter credentials.
                                                    </div>
                                                }
                                                @if (gitPolicies.Count > 0)
                                                {
                                                    <div class="alert alert-secondary py-2 small mb-2">
                                                        <i class="bi bi-shield-check me-1"></i>
                                                        Repository URL must match one of your allowed patterns:
                                                        @foreach (CustomerGitRepoPolicy p in gitPolicies)
                                                        {
                                                            <code class="ms-2">@p.UrlPattern</code>
                                                        }
                                                    </div>
                                                }
                                            </div>
                                            <div class="col-12">
                                                <label class="form-label small">Repository URL</label>
                                                <input class="form-control form-control-sm font-monospace @(newGitUrlError is not null ? "is-invalid" : "")"
                                                       @bind="newGitUrl" @bind:event="oninput" @onchange="ValidateNewGitUrl"
                                                       placeholder="https://github.com/org/repo" />
                                                @if (newGitUrlError is not null)
                                                {
                                                    <div class="invalid-feedback">@newGitUrlError</div>
                                                }
                                            </div>
                                            <div class="col-md-6">
                                                <label class="form-label small">Path <span class="text-muted">(leave blank for repo root)</span></label>
                                                <input class="form-control form-control-sm font-monospace" @bind="newGitPath" placeholder="." />
                                            </div>
                                            <div class="col-md-6">
                                                <label class="form-label small">Branch / Revision <span class="text-muted">(leave blank for default)</span></label>
                                                <input class="form-control form-control-sm font-monospace" @bind="newGitRevision" placeholder="main" />
                                            </div>
                                        }
                                    </div>
                                    <div class="d-flex gap-1">
                                        <button class="btn btn-sm btn-primary" @onclick="() => CreateDeployment(app.Id)"
                                                disabled="@(string.IsNullOrWhiteSpace(newDeployName) || newDeployEnvId == Guid.Empty || newDeployClusterId == Guid.Empty || string.IsNullOrWhiteSpace(newDeployNamespace) || creatingDeployment || newGitUrlError is not null || (IsGitType(newDeployType) && string.IsNullOrWhiteSpace(newGitUrl)))">
                                            @if (creatingDeployment) { <span class="spinner-border spinner-border-sm me-1"></span> }
                                            Create
                                        </button>
                                        <button class="btn btn-sm btn-outline-secondary" @onclick="() => createDeployAppId = null">Cancel</button>
                                    </div>
                                </div>
                            </div>
                        }

                        @if (appDeployments.TryGetValue(app.Id, out List<AppDeployment>? deploys) && deploys.Count > 0)
                        {
                            <div class="row row-cols-1 row-cols-lg-2 g-2">
                                @foreach (AppDeployment deploy in deploys)
                                {
                                    <div class="col">
                                        <div class="card border-light" role="button" style="cursor: pointer;"
                                             @onclick="() => SelectDeployment(deploy)">
                                            <div class="card-body py-2">
                                                <div class="d-flex align-items-center justify-content-between mb-1">
                                                    <span class="fw-medium">
                                                        <i class="bi bi-rocket-takeoff me-1 text-primary"></i>@deploy.Name
                                                    </span>
                                                    <div class="d-flex gap-1">
                                                        @SyncBadge(deploy.SyncStatus)
                                                        @HealthBadge(deploy.HealthStatus)
                                                    </div>
                                                </div>
                                                <div class="d-flex flex-wrap gap-2 text-muted small">
                                                    @TypeBadge(deploy.Type)
                                                    <span><i class="bi bi-layers me-1"></i>@deploy.Environment?.Name</span>
                                                    <span><i class="bi bi-hdd-network me-1"></i>@deploy.Cluster?.Name</span>
                                                    <span><i class="bi bi-box me-1"></i>@deploy.Namespace</span>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                }
                            </div>
                        }
                        else if (!showingCreateForm)
                        {
                            <EmptyState Icon="bi-rocket-takeoff"
                                        Message="No deployments configured for this app yet." />
                        }
                    </div>
                </div>
            }
        }
    }

    @* ════════════════════════════════════════════════════════════════
       Level 3: Deployment detail with operations
       ════════════════════════════════════════════════════════════════ *@
    else if (selectedDeployment is not null)
    {
        <PortalDeploymentDetail
            Deployment="selectedDeployment"
            AccessRole="currentAccessRole"
            K8sOps="K8sOps"
            DeploymentService="DeploymentService"
            PrometheusService="PrometheusService"
            AuditService="AuditService"
            CustomerId="selectedCustomer!.Id"
            TenantId="selectedCustomer!.TenantId"
            GovernanceNamespace="@(selectedCustomer?.Apps.FirstOrDefault(a => a.Id == selectedDeployment.AppId)?.Namespace)"
            OnBack="BackToApps"
            OnDeleted="OnDeploymentDeleted" />
    }

    @* ════════════════════════════════════════════════════════════════
       Level 3: Identity (Keycloak realm) management
       ════════════════════════════════════════════════════════════════ *@
    else if (selectedIdentityRealm is not null)
    {
        <PortalIdentityDetail
            Realm="selectedIdentityRealm"
            TenantId="selectedCustomer!.TenantId" />
    }

    @* ════════════════════════════════════════════════════════════════
       Level 3: App secrets vault
       ════════════════════════════════════════════════════════════════ *@
    else if (selectedSecretsApp is not null)
    {
        <PortalSecretsDetail
            App="selectedSecretsApp"
            TenantId="selectedCustomer!.TenantId"
            AccessRole="currentAccessRole" />
    }

    @* ════════════════════════════════════════════════════════════════
       Level 3: Harbor registry project management
       ════════════════════════════════════════════════════════════════ *@
    else if (selectedRegistryProject is not null)
    {
        <PortalRegistryDetail
            Project="selectedRegistryProject"
            TenantId="selectedCustomer!.TenantId"
            AccessRole="currentAccessRole" />
    }
    else if (selectedGovernanceApp is not null)
    {
        <AppGovernancePanel AppId="selectedGovernanceApp.Id" AppName="selectedGovernanceApp.Name" />
    }
    else if (selectedResourcesApp is not null)
    {
        <AppResourcesPanel AppId="selectedResourcesApp.Id" />
    }
}

@code {
    private bool loading = true;
    private string? currentUserId;
    private CustomerAccessRole currentAccessRole;

    private List<Customer>? customers;
    private Customer? selectedCustomer;
    private AppDeployment? selectedDeployment;
    private KeycloakRealm? selectedIdentityRealm;
    private Data.App? selectedSecretsApp;
    private HarborProject? selectedRegistryProject;
    private Data.App? selectedGovernanceApp;
    private Data.App? selectedResourcesApp;

    // Deployments indexed by app ID for the selected customer.
    private Dictionary<Guid, List<AppDeployment>> appDeployments = new();

    // Linked Keycloak realms indexed by app ID for the selected customer.
    private Dictionary<Guid, KeycloakRealm> appRealms = new();

    // Linked Harbor projects indexed by app ID for the selected customer.
    private Dictionary<Guid, HarborProject> appHarborProjects = new();

    // Environments and clusters for deployment creation (loaded when Admin selects a customer).
    private List<Data.Environment> tenantEnvironments = [];
    private List<KubernetesCluster> tenantClusters = [];

    // Monitoring panel toggle in Level 2
    private bool showMonitoringPanel;

    private List<AppDeployment> AllCustomerDeployments =>
        appDeployments.Values.SelectMany(d => d).ToList();

    private HashSet<Guid> AllCustomerClusterIds =>
        AllCustomerDeployments.Select(d => d.ClusterId).ToHashSet();

    // Git policies and credentials for the selected customer.
    private List<CustomerGitRepoPolicy> gitPolicies = [];
    private List<CustomerGitCredential> customerGitCredentials = [];
    private bool showGitPolicies;

    // Create deployment form (portal Admin).
    private Guid? createDeployAppId;
    private string newDeployName = "", newDeployNamespace = "";
    private DeploymentType newDeployType = DeploymentType.Manual;
    private Guid newDeployEnvId, newDeployClusterId;
    private string newHelmRepoUrl = "", newHelmChartName = "", newHelmChartVersion = "";
    private string newGitUrl = "", newGitPath = "", newGitRevision = "";
    private string? newGitUrlError;
    private bool creatingDeployment;
    private string? createDeployError;

    protected override async Task OnInitializedAsync()
    {
        // Determine who the logged-in user is.
        AuthenticationState authState = await AuthStateProvider.GetAuthenticationStateAsync();
        currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;

        if (string.IsNullOrEmpty(currentUserId))
        {
            loading = false;
            return;
        }

        // Load only the customers this user has access to.
        customers = await CustomerAccessService.GetAccessibleCustomersAsync(currentUserId);
        loading = false;
    }

    private async Task SelectCustomer(Customer customer)
    {
        selectedCustomer = customer;
        showMonitoringPanel = false;
        showGitPolicies = false;

        // Look up the user's role for this customer.
        CustomerAccess? access = await CustomerAccessService.GetAccessAsync(currentUserId!, customer.Id);
        currentAccessRole = access?.Role ?? CustomerAccessRole.Viewer;

        // Load deployments, linked Keycloak realms, and linked Harbor projects for all apps.
        appDeployments.Clear();
        appRealms.Clear();
        appHarborProjects.Clear();
        createDeployAppId = null;

        // Load git policies and credentials for this customer (all roles need policies to validate URLs).
        gitPolicies = await CustomerGitService.GetRepoPoliciesAsync(customer.Id);
        customerGitCredentials = await CustomerGitService.GetCredentialsAsync(customer.Id);

        foreach (Data.App app in customer.Apps)
        {
            List<AppDeployment> deploys = await DeploymentService.GetDeploymentsAsync(app.Id);
            appDeployments[app.Id] = deploys;

            KeycloakRealm? realm = await KeycloakService.GetRealmForAppAsync(customer.TenantId, app.Id);
            if (realm is not null)
                appRealms[app.Id] = realm;

            HarborProject? harborProject = await HarborService.GetProjectForAppAsync(customer.TenantId, app.Id);
            if (harborProject is not null)
                appHarborProjects[app.Id] = harborProject;
        }

        // Load environments and clusters so Admin users can create deployments.
        if (currentAccessRole >= CustomerAccessRole.Admin)
        {
            tenantEnvironments = await TenantService.GetEnvironmentsAsync(customer.TenantId);
            tenantClusters = await TenantService.GetClustersAsync(customer.TenantId);
        }
    }

    private void SelectDeployment(AppDeployment deploy)
    {
        selectedDeployment = deploy;
    }

    private void SelectIdentityRealm(Guid appId)
    {
        if (appRealms.TryGetValue(appId, out KeycloakRealm? realm))
            selectedIdentityRealm = realm;
    }

    private void SelectRegistryProject(Guid appId)
    {
        if (appHarborProjects.TryGetValue(appId, out HarborProject? project))
            selectedRegistryProject = project;
    }

    private void BackToCustomers()
    {
        selectedCustomer = null;
        selectedDeployment = null;
        selectedIdentityRealm = null;
        selectedSecretsApp = null;
        selectedRegistryProject = null;
        gitPolicies.Clear();
        customerGitCredentials.Clear();
        showGitPolicies = false;
    }

    private void SelectSecretsApp(Data.App app)
    {
        selectedSecretsApp = app;
    }

    private void SelectGovernanceApp(Data.App app)
    {
        selectedGovernanceApp = app;
    }

    private void SelectResourcesApp(Data.App app)
    {
        selectedResourcesApp = app;
    }

    private void BackToApps()
    {
        selectedDeployment = null;
        selectedIdentityRealm = null;
        selectedSecretsApp = null;
        selectedRegistryProject = null;
        selectedGovernanceApp = null;
        selectedResourcesApp = null;
        showGitPolicies = false;
    }

    private async Task OnDeploymentDeleted()
    {
        if (selectedDeployment is not null)
        {
            Guid appId = selectedDeployment.AppId;
            selectedDeployment = null;
            // Reload the affected app's deployments.
            appDeployments[appId] = await DeploymentService.GetDeploymentsAsync(appId);
        }
    }

    private void ToggleCreateDeployment(Guid appId)
    {
        if (createDeployAppId == appId)
        {
            createDeployAppId = null;
        }
        else
        {
            createDeployAppId = appId;
            Data.App? app = selectedCustomer?.Apps.FirstOrDefault(a => a.Id == appId);
            newDeployNamespace = app?.Namespace ?? "";
            newDeployName = newHelmRepoUrl = newHelmChartName = newHelmChartVersion = "";
            newGitUrl = newGitPath = newGitRevision = "";
            newGitUrlError = null;
            newDeployType = DeploymentType.Manual;
            newDeployEnvId = newDeployClusterId = Guid.Empty;
            createDeployError = null;
        }
    }

    private async Task CreateDeployment(Guid appId)
    {
        creatingDeployment = true;
        createDeployError = null;

        try
        {
            Guid? gitRepoId = null;

            if (IsGitType(newDeployType) && !string.IsNullOrWhiteSpace(newGitUrl))
            {
                // Validate URL against policy one more time before creating.
                if (!CustomerGitService.MatchesAnyPolicy(newGitUrl, gitPolicies))
                {
                    createDeployError = "The repository URL does not match any allowed policy pattern.";
                    return;
                }

                // Find or create a GitRepository backed by the first available credential.
                CustomerGitCredential? cred = customerGitCredentials.FirstOrDefault();
                if (cred is not null && selectedCustomer is not null)
                {
                    GitRepository repo = await CustomerGitService.FindOrCreateRepositoryForCustomerAsync(
                        selectedCustomer.TenantId, selectedCustomer.Id, newGitUrl, cred.Id);
                    gitRepoId = repo.Id;
                }
            }

            await DeploymentService.CreateDeploymentAsync(
                appId, newDeployName, newDeployType,
                newDeployEnvId, newDeployClusterId, newDeployNamespace,
                newDeployType == DeploymentType.HelmChart ? newHelmRepoUrl : null,
                newDeployType == DeploymentType.HelmChart ? newHelmChartName : null,
                newDeployType == DeploymentType.HelmChart ? newHelmChartVersion : null,
                gitRepositoryId: gitRepoId,
                gitPath: string.IsNullOrWhiteSpace(newGitPath) ? null : newGitPath,
                gitRevision: string.IsNullOrWhiteSpace(newGitRevision) ? null : newGitRevision);

            createDeployAppId = null;
            appDeployments[appId] = await DeploymentService.GetDeploymentsAsync(appId);
        }
        catch (Exception ex)
        {
            createDeployError = ex.Message;
        }
        finally
        {
            creatingDeployment = false;
        }
    }

    private void ValidateNewGitUrl(ChangeEventArgs e)
    {
        newGitUrl = e.Value?.ToString() ?? "";
        if (string.IsNullOrWhiteSpace(newGitUrl))
        {
            newGitUrlError = null;
            return;
        }
        newGitUrlError = CustomerGitService.MatchesAnyPolicy(newGitUrl, gitPolicies)
            ? null
            : "URL does not match any allowed policy pattern.";
    }

    private static bool IsGitType(DeploymentType type)
        => type is DeploymentType.GitYaml or DeploymentType.GitHelm or DeploymentType.GitAppOfApps;

    // ──────── Badge helpers ────────

    private RenderFragment TypeBadge(DeploymentType type) => type switch
    {
        DeploymentType.Manual => @<span class="badge bg-info">Manual</span>,
        DeploymentType.Yaml => @<span class="badge bg-warning text-dark">YAML</span>,
        DeploymentType.HelmChart => @<span class="badge bg-purple text-white" style="background-color: #6f42c1 !important;">Helm</span>,
        DeploymentType.GitYaml => @<span class="badge bg-success"><i class="bi bi-git me-1"></i>Git YAML</span>,
        DeploymentType.GitHelm => @<span class="badge bg-success"><i class="bi bi-git me-1"></i>Git Helm</span>,
        DeploymentType.GitAppOfApps => @<span class="badge bg-success"><i class="bi bi-git me-1"></i>App of Apps</span>,
        _ => @<span class="badge bg-secondary">Unknown</span>
    };

    private RenderFragment SyncBadge(SyncStatus status) => status switch
    {
        SyncStatus.Synced => @<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Synced</span>,
        SyncStatus.OutOfSync => @<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-circle me-1"></i>OutOfSync</span>,
        SyncStatus.Syncing => @<span class="badge bg-info"><i class="bi bi-arrow-repeat me-1"></i>Syncing</span>,
        SyncStatus.Failed => @<span class="badge bg-danger"><i class="bi bi-x-circle me-1"></i>Failed</span>,
        _ => @<span class="badge bg-secondary">Unknown</span>
    };

    private RenderFragment HealthBadge(HealthStatus status) => status switch
    {
        HealthStatus.Healthy => @<span class="badge bg-success"><i class="bi bi-heart-fill me-1"></i>Healthy</span>,
        HealthStatus.Progressing => @<span class="badge bg-info"><i class="bi bi-hourglass-split me-1"></i>Progressing</span>,
        HealthStatus.Degraded => @<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-triangle me-1"></i>Degraded</span>,
        HealthStatus.Missing => @<span class="badge bg-danger"><i class="bi bi-question-circle me-1"></i>Missing</span>,
        HealthStatus.Suspended => @<span class="badge bg-secondary"><i class="bi bi-pause-circle me-1"></i>Suspended</span>,
        _ => @<span class="badge bg-secondary">Unknown</span>
    };
}
