using Microsoft.Extensions.Logging;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Orchestrates the 6-step Authentik invitation infrastructure setup for a customer.
/// All operations are idempotent — the underlying Authentik API methods check for
/// existing resources before creating new ones.
///
/// Per customer, this creates:
///   1. A group (<c>customer-{abbrev}</c>)
///   2. An invitation stage (<c>{abbrev}-invitation-stage</c>)
///   3. An enrollment flow (<c>{abbrev}-enrollment</c>) with stages bound in order
///   4. An expression policy on the UserWrite stage to auto-assign users to the group
///   5. A role (<c>{abbrev}-invite-manager</c>) with invitation CRUD permissions
///   6. A scoping policy on the flow so only group members can access it
/// </summary>
public class InvitationSetupService : IInvitationSetupService
{
    private readonly IAuthentikService _authentik;
    private readonly SettingsService _settings;
    private readonly ILogger<InvitationSetupService> _logger;

    public InvitationSetupService(
        IAuthentikService authentik,
        SettingsService settings,
        ILogger<InvitationSetupService> logger)
    {
        _authentik = authentik;
        _settings = settings;
        _logger = logger;
    }

    /// <inheritdoc />
    public async Task<InvitationSetupResult> SetupCustomerInvitationAsync(
        string customerAbbrev,
        string customerName,
        CancellationToken ct = default)
    {
        var abbrev = customerAbbrev.Trim().ToLowerInvariant();
        var groupName = $"customer-{abbrev}";
        var flowSlug = $"{abbrev}-enrollment";
        var flowName = $"{customerName} Enrollment";
        var roleName = $"{abbrev}-invite-manager";
        var invitationStageName = $"{abbrev}-invitation-stage";

        _logger.LogInformation(
            "[InviteSetup] Starting invitation infrastructure setup for customer '{Customer}' (abbrev={Abbrev})",
            customerName, abbrev);

        try
        {
            // ═══════════════════════════════════════════════════════════════════
            // Step 1: Create customer group
            // ═══════════════════════════════════════════════════════════════════
            _logger.LogInformation("[InviteSetup] Step 1/6: Creating customer group '{Group}'", groupName);
            var groupPk = await _authentik.CreateGroupAsync(groupName, ct);

            // ═══════════════════════════════════════════════════════════════════
            // Step 2: Create invitation stage
            // ═══════════════════════════════════════════════════════════════════
            _logger.LogInformation("[InviteSetup] Step 2/6: Creating invitation stage '{Stage}'", invitationStageName);
            var invitationStagePk = await _authentik.CreateInvitationStageAsync(
                invitationStageName, continueWithoutInvitation: false, ct);

            // ═══════════════════════════════════════════════════════════════════
            // Step 3: Create enrollment flow and bind stages
            // ═══════════════════════════════════════════════════════════════════
            _logger.LogInformation("[InviteSetup] Step 3/6: Creating enrollment flow '{Slug}'", flowSlug);
            var flowPk = await _authentik.CreateEnrollmentFlowAsync(flowName, flowSlug, ct);

            // Resolve built-in Authentik stages for the enrollment pipeline
            var promptStagePk = await _authentik.FindStageByNameAsync("default-enrollment-prompt", ct);
            var userWriteStagePk = await _authentik.FindStageByNameAsync("default-enrollment-user-write", ct);
            var userLoginStagePk = await _authentik.FindStageByNameAsync("default-enrollment-user-login", ct);

            // Bind stages in order: 10=Invitation, 20=Prompt, 30=UserWrite, 40=UserLogin
            await _authentik.BindStageToFlowAsync(flowSlug, invitationStagePk, 10, ct);

            if (promptStagePk != null)
            {
                await _authentik.BindStageToFlowAsync(flowSlug, promptStagePk, 20, ct);
                _logger.LogInformation("[InviteSetup] Bound default prompt stage at order 20");
            }
            else
            {
                _logger.LogWarning("[InviteSetup] Built-in 'default-enrollment-prompt' stage not found — " +
                                   "you may need to create a prompt stage manually and bind it to flow '{Slug}' at order 20", flowSlug);
            }

            if (userWriteStagePk != null)
            {
                await _authentik.BindStageToFlowAsync(flowSlug, userWriteStagePk, 30, ct);
                _logger.LogInformation("[InviteSetup] Bound default user-write stage at order 30");
            }
            else
            {
                _logger.LogWarning("[InviteSetup] Built-in 'default-enrollment-user-write' stage not found — " +
                                   "you may need to create a user-write stage manually and bind it to flow '{Slug}' at order 30", flowSlug);
            }

            if (userLoginStagePk != null)
            {
                await _authentik.BindStageToFlowAsync(flowSlug, userLoginStagePk, 40, ct);
                _logger.LogInformation("[InviteSetup] Bound default user-login stage at order 40");
            }
            else
            {
                _logger.LogWarning("[InviteSetup] Built-in 'default-enrollment-user-login' stage not found — " +
                                   "you may need to create a user-login stage manually and bind it to flow '{Slug}' at order 40", flowSlug);
            }

            // ═══════════════════════════════════════════════════════════════════
            // Step 4: Create group-assignment policy and bind to UserWrite stage
            // ═══════════════════════════════════════════════════════════════════
            _logger.LogInformation("[InviteSetup] Step 4/6: Creating group-assignment expression policy");

            var groupAssignPolicyName = $"{abbrev}-auto-assign-group";
            var groupAssignExpression = $"""
                from authentik.core.models import Group

                # Auto-assign to customer group on registration
                group = Group.objects.filter(name="{groupName}").first()
                if group and context.get("pending_user"):
                    context["pending_user"].ak_groups.add(group)

                return True
                """;

            var groupAssignPolicyPk = await _authentik.CreateExpressionPolicyAsync(
                groupAssignPolicyName, groupAssignExpression, ct);

            // Bind policy to the UserWrite stage binding (order 30)
            if (userWriteStagePk != null)
            {
                var userWriteBindingPk = await _authentik.GetFlowStageBindingPkAsync(flowSlug, 30, ct);
                if (userWriteBindingPk != null)
                {
                    await _authentik.BindPolicyToFlowStageBoundAsync(userWriteBindingPk, groupAssignPolicyPk, ct);
                    _logger.LogInformation("[InviteSetup] Group-assignment policy bound to UserWrite stage");
                }
                else
                {
                    _logger.LogWarning("[InviteSetup] Could not find flow-stage binding at order 30 to attach group policy");
                }
            }

            // ═══════════════════════════════════════════════════════════════════
            // Step 5: Create invite-manager role with permissions
            // ═══════════════════════════════════════════════════════════════════
            _logger.LogInformation("[InviteSetup] Step 5/6: Creating invite-manager role '{Role}'", roleName);
            var rolePk = await _authentik.CreateRoleAsync(roleName, ct);

            // Assign invitation CRUD permissions
            var invitationPermissions = new[]
            {
                "add_invitation",
                "view_invitation",
                "delete_invitation",
            };
            await _authentik.AssignPermissionsToRoleAsync(rolePk, invitationPermissions, ct);

            // Assign role to the customer group
            await _authentik.AssignRoleToGroupAsync(rolePk, groupPk, ct);
            _logger.LogInformation("[InviteSetup] Role '{Role}' assigned to group '{Group}'", roleName, groupName);

            // ═══════════════════════════════════════════════════════════════════
            // Step 6: Create scoping policy and bind to flow
            // ═══════════════════════════════════════════════════════════════════
            _logger.LogInformation("[InviteSetup] Step 6/6: Creating invitation scoping policy");

            var scopePolicyName = $"scope-invitations-to-{abbrev}";
            var scopeExpression = $"""
                # Only allow users in {groupName} group to manage these invitations
                user = context.get("pending_user") or request.user

                if user.ak_groups.filter(name="{groupName}").exists():
                    return True

                return False
                """;

            var scopePolicyPk = await _authentik.CreateExpressionPolicyAsync(scopePolicyName, scopeExpression, ct);

            // Bind scoping policy to the enrollment flow
            await _authentik.BindPolicyToFlowAsync(flowSlug, scopePolicyPk, ct);
            _logger.LogInformation("[InviteSetup] Scoping policy bound to enrollment flow");

            // ═══════════════════════════════════════════════════════════════════
            // Build result with management URL
            // ═══════════════════════════════════════════════════════════════════
            var authentikUrl = await _settings.GetAsync(SettingsService.AuthentikUrl);
            var managementUrl = !string.IsNullOrWhiteSpace(authentikUrl)
                ? $"{authentikUrl.TrimEnd('/')}/if/admin/#/core/invitations"
                : null;

            var result = new InvitationSetupResult
            {
                Success = true,
                Message = $"Invitation infrastructure created for {customerName}. " +
                          $"Group: {groupName}, Flow: {flowSlug}, Role: {roleName}.",
                GroupName = groupName,
                EnrollmentFlowSlug = flowSlug,
                RoleName = roleName,
                InvitationManagementUrl = managementUrl,
            };

            _logger.LogInformation(
                "[InviteSetup] Setup complete for '{Customer}': group={Group}, flow={Flow}, role={Role}, url={Url}",
                customerName, groupName, flowSlug, roleName, managementUrl ?? "(no URL)");

            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "[InviteSetup] Invitation setup failed for '{Customer}' (abbrev={Abbrev}): {Message}",
                customerName, abbrev, ex.Message);

            return new InvitationSetupResult
            {
                Success = false,
                Message = $"Invitation setup failed: {ex.Message}",
                GroupName = groupName,
                EnrollmentFlowSlug = flowSlug,
                RoleName = roleName,
            };
        }
    }
}
