using System.Security.Cryptography;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OTSSignsOrchestrator.Core.Data;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Runs once after a Xibo CMS stack is deployed to complete post-install setup:
/// <list type="number">
///   <item>Waits for the Xibo web service to become available.</item>
///   <item>Creates the OTS admin user with a random password.</item>
///   <item>Registers a dedicated client_credentials OAuth2 application for OTS.</item>
///   <item>Activates the <c>otssigns</c> theme.</item>
///   <item>Stores all generated credentials in Bitwarden Secrets Manager.</item>
/// </list>
///
/// Invoked as a background fire-and-forget task from <see cref="InstanceService.CreateInstanceAsync"/>.
/// Progress is tracked in the operation log; failures are logged but do not roll back the deployment.
/// </summary>
public class PostInstanceInitService
{
    private readonly IServiceProvider _services;
    private readonly ILogger<PostInstanceInitService> _logger;

    // How long to wait for Xibo to become healthy before aborting post-init.
    private static readonly TimeSpan XiboReadinessTimeout = TimeSpan.FromMinutes(15);

    public PostInstanceInitService(
        IServiceProvider services,
        ILogger<PostInstanceInitService> logger)
    {
        _services = services;
        _logger   = logger;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Entry point
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Executes the post-instance initialisation sequence.
    /// Intended to be called as a background task after stack deployment succeeds.
    /// </summary>
    public async Task RunAsync(string abbrev, string instanceUrl, CancellationToken ct = default)
    {
        _logger.LogInformation("[PostInit] Starting post-instance init for {Abbrev} ({Url})", abbrev, instanceUrl);

        try
        {
            using var scope      = _services.CreateScope();
            var xibo             = scope.ServiceProvider.GetRequiredService<XiboApiService>();
            var bws              = scope.ServiceProvider.GetRequiredService<IBitwardenSecretService>();
            var settings         = scope.ServiceProvider.GetRequiredService<SettingsService>();
            var db               = scope.ServiceProvider.GetRequiredService<XiboContext>();

            // ── Validate Bitwarden is configured ────────────────────────────
            if (!await bws.IsConfiguredAsync())
            {
                _logger.LogWarning(
                    "[PostInit] Bitwarden is not configured — credentials will NOT be stored in Bitwarden. " +
                    "Configure Settings → Bitwarden to enable secret storage.");
            }

            // ── Read bootstrap credentials ───────────────────────────────────
            var bootstrapClientId     = await settings.GetAsync(SettingsService.XiboBootstrapClientId);
            var bootstrapClientSecret = await settings.GetAsync(SettingsService.XiboBootstrapClientSecret);

            if (string.IsNullOrWhiteSpace(bootstrapClientId) || string.IsNullOrWhiteSpace(bootstrapClientSecret))
                throw new InvalidOperationException(
                    "Xibo bootstrap OAuth2 credentials are not configured. " +
                    "Set Settings → Xibo Bootstrap Client ID / Secret to enable post-instance setup.");

            // ── 1. Wait for Xibo readiness ────────────────────────────────────
            _logger.LogInformation("[PostInit] Waiting for Xibo to become ready at {Url}...", instanceUrl);
            var ready = await xibo.WaitForReadyAsync(instanceUrl, XiboReadinessTimeout, ct);
            if (!ready)
                throw new TimeoutException(
                    $"Xibo instance at {instanceUrl} did not become ready within {XiboReadinessTimeout.TotalMinutes} minutes.");

            // ── 2. Generate credentials ───────────────────────────────────────
            var adminUsername = $"ots-admin-{abbrev}";
            var adminPassword = GeneratePassword(24);
            var adminEmail    = $"ots-admin-{abbrev}@ots-signs.com";

            // ── 3. Create OTS admin user ──────────────────────────────────────
            _logger.LogInformation("[PostInit] Creating OTS admin user '{Username}'", adminUsername);
            int userId = await xibo.CreateAdminUserAsync(
                instanceUrl, bootstrapClientId, bootstrapClientSecret,
                adminUsername, adminPassword, adminEmail);

            // ── 4. Register dedicated OAuth2 application ──────────────────────
            _logger.LogInformation("[PostInit] Registering OTS OAuth2 application");
            var (oauthClientId, oauthClientSecret) = await xibo.RegisterOAuthClientAsync(
                instanceUrl, bootstrapClientId, bootstrapClientSecret,
                appName: $"OTS Signs — {abbrev.ToUpperInvariant()}");

            // ── 5. Set theme ──────────────────────────────────────────────────
            _logger.LogInformation("[PostInit] Setting Xibo theme to 'otssigns'");
            await xibo.SetThemeAsync(instanceUrl, bootstrapClientId, bootstrapClientSecret, "otssigns");

            // ── 6. Store credentials in Bitwarden ─────────────────────────────
            if (await bws.IsConfiguredAsync())
            {
                _logger.LogInformation("[PostInit] Storing credentials in Bitwarden");

                var adminSecretId = await bws.CreateSecretAsync(
                    key:   $"{abbrev}/xibo-admin-password",
                    value: adminPassword,
                    note:  $"Xibo CMS admin password for instance {abbrev}. Username: {adminUsername}");

                var oauthSecretId = await bws.CreateSecretAsync(
                    key:   $"{abbrev}/xibo-oauth-secret",
                    value: oauthClientSecret,
                    note:  $"Xibo CMS OAuth2 client secret for OTS application. ClientId: {oauthClientId}");

                // Persist Bitwarden secret IDs + OAuth client ID in AppSettings for later retrieval
                await settings.SetAsync(SettingsService.InstanceAdminPasswordSecretId(abbrev), adminSecretId,
                    SettingsService.CatInstance, isSensitive: false);
                await settings.SetAsync(SettingsService.InstanceOAuthSecretId(abbrev), oauthSecretId,
                    SettingsService.CatInstance, isSensitive: false);
                await settings.SetAsync(SettingsService.InstanceOAuthClientId(abbrev), oauthClientId,
                    SettingsService.CatInstance, isSensitive: false);
            }
            else
            {
                // No Bitwarden — fall back to encrypted AppSettings (less ideal for secrets)
                _logger.LogWarning(
                    "[PostInit] Bitwarden not configured. Storing admin password locally (encrypted). " +
                    "OAuth client secret is discarded — re-register from the instance details modal.");

                await settings.SetAsync(
                    $"Instance.{abbrev}.AdminPassword", adminPassword,
                    SettingsService.CatInstance, isSensitive: true);
                await settings.SetAsync(SettingsService.InstanceOAuthClientId(abbrev), oauthClientId,
                    SettingsService.CatInstance, isSensitive: false);
            }

            await db.SaveChangesAsync(ct);

            _logger.LogInformation(
                "[PostInit] Post-instance init complete for {Abbrev}. Admin user: {Username}, OAuth ClientId: {ClientId}",
                abbrev, adminUsername, oauthClientId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[PostInit] Post-instance init failed for {Abbrev}: {Message}", abbrev, ex.Message);
            // Do NOT rethrow — the stack is already deployed; we don't want to break the deployment result.
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Credential rotation (called from the UI)
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Generates a new OTS admin password, updates it in Xibo, and rotates the Bitwarden secret.
    /// </summary>
    public async Task<string> RotateAdminPasswordAsync(string abbrev, string instanceUrl)
    {
        _logger.LogInformation("[PostInit] Rotating admin password for {Abbrev}", abbrev);

        using var scope   = _services.CreateScope();
        var xibo          = scope.ServiceProvider.GetRequiredService<XiboApiService>();
        var bws           = scope.ServiceProvider.GetRequiredService<IBitwardenSecretService>();
        var settings      = scope.ServiceProvider.GetRequiredService<SettingsService>();
        var db            = scope.ServiceProvider.GetRequiredService<XiboContext>();

        // Get bootstrap credentials to authenticate against Xibo API
        var bootstrapClientId     = await settings.GetAsync(SettingsService.XiboBootstrapClientId)
                                    ?? throw new InvalidOperationException("Xibo bootstrap client not configured.");
        var bootstrapClientSecret = await settings.GetAsync(SettingsService.XiboBootstrapClientSecret)
                                    ?? throw new InvalidOperationException("Xibo bootstrap client secret not configured.");

        // Xibo user ID: we store it as the numeric userId in the username convention
        // We need to look it up from the admin username. For now derive from OTS convention.
        var adminUsername  = $"ots-admin-{abbrev}";
        var newPassword    = GeneratePassword(24);

        // We need to look up the userId — use the Xibo API (list users, find by userName)
        var userId = await GetXiboUserIdAsync(xibo, instanceUrl, bootstrapClientId, bootstrapClientSecret, adminUsername);

        await xibo.RotateUserPasswordAsync(instanceUrl, bootstrapClientId, bootstrapClientSecret, userId, newPassword);

        // Update Bitwarden secret if available
        if (await bws.IsConfiguredAsync())
        {
            var secretId = await settings.GetAsync(SettingsService.InstanceAdminPasswordSecretId(abbrev));
            if (!string.IsNullOrWhiteSpace(secretId))
            {
                await bws.UpdateSecretAsync(secretId,
                    key:   $"{abbrev}/xibo-admin-password",
                    value: newPassword,
                    note:  $"Rotated on {DateTime.UtcNow:yyyy-MM-dd HH:mm} UTC. Username: {adminUsername}");
            }
            else
            {
                // Secret doesn't exist yet in Bitwarden — create it now
                var newSecretId = await bws.CreateSecretAsync(
                    $"{abbrev}/xibo-admin-password", newPassword,
                    $"Rotated on {DateTime.UtcNow:yyyy-MM-dd HH:mm} UTC. Username: {adminUsername}");
                await settings.SetAsync(SettingsService.InstanceAdminPasswordSecretId(abbrev), newSecretId,
                    SettingsService.CatInstance, isSensitive: false);
            }
        }
        else
        {
            // Fallback: encrypted AppSettings
            await settings.SetAsync($"Instance.{abbrev}.AdminPassword", newPassword,
                SettingsService.CatInstance, isSensitive: true);
        }

        await db.SaveChangesAsync();
        _logger.LogInformation("[PostInit] Admin password rotated for {Abbrev}", abbrev);
        return newPassword;
    }

    /// <summary>
    /// Returns the stored admin password for an instance (from Bitwarden or local AppSettings).
    /// </summary>
    public async Task<InstanceCredentials> GetCredentialsAsync(string abbrev)
    {
        using var scope  = _services.CreateScope();
        var bws          = scope.ServiceProvider.GetRequiredService<IBitwardenSecretService>();
        var settings     = scope.ServiceProvider.GetRequiredService<SettingsService>();

        var adminUsername  = $"ots-admin-{abbrev}";
        var oauthClientId  = await settings.GetAsync(SettingsService.InstanceOAuthClientId(abbrev));
        string? adminPassword = null;
        string? oauthClientSecret = null;

        if (await bws.IsConfiguredAsync())
        {
            var adminSecretId = await settings.GetAsync(SettingsService.InstanceAdminPasswordSecretId(abbrev));
            if (!string.IsNullOrWhiteSpace(adminSecretId))
            {
                try
                {
                    var secret = await bws.GetSecretAsync(adminSecretId);
                    adminPassword = secret.Value;
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Could not retrieve admin password from Bitwarden for {Abbrev}", abbrev);
                }
            }

            var oauthSecretId = await settings.GetAsync(SettingsService.InstanceOAuthSecretId(abbrev));
            if (!string.IsNullOrWhiteSpace(oauthSecretId))
            {
                try
                {
                    var secret = await bws.GetSecretAsync(oauthSecretId);
                    oauthClientSecret = secret.Value;
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Could not retrieve OAuth secret from Bitwarden for {Abbrev}", abbrev);
                }
            }
        }
        else
        {
            adminPassword = await settings.GetAsync($"Instance.{abbrev}.AdminPassword");
        }

        return new InstanceCredentials
        {
            AdminUsername     = adminUsername,
            AdminPassword     = adminPassword,
            OAuthClientId     = oauthClientId,
            OAuthClientSecret = oauthClientSecret,
        };
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────────────────────

    private static string GeneratePassword(int length)
    {
        const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
        return RandomNumberGenerator.GetString(chars, length);
    }

    private static async Task<int> GetXiboUserIdAsync(
        XiboApiService xibo,
        string instanceUrl,
        string clientId,
        string clientSecret,
        string targetUsername)
    {
        // This is a lightweight HTTP GET to fetch the user list and find by name.
        // We use HttpClient directly since XiboApiService doesn't expose this as a standalone call.
        using var http = new System.Net.Http.HttpClient();
        var baseUrl    = instanceUrl.TrimEnd('/');

        // Get token first via the TestConnectionAsync pattern
        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type",    "client_credentials"),
            new KeyValuePair<string, string>("client_id",     clientId),
            new KeyValuePair<string, string>("client_secret", clientSecret),
        });

        var tokenResp = await http.PostAsync($"{baseUrl}/api/authorize/access_token", form);
        tokenResp.EnsureSuccessStatusCode();

        using var tokenDoc    = await System.Text.Json.JsonDocument.ParseAsync(
                                    await tokenResp.Content.ReadAsStreamAsync());
        var token             = tokenDoc.RootElement.GetProperty("access_token").GetString()!;

        http.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var usersResp = await http.GetAsync($"{baseUrl}/api/user?userName={Uri.EscapeDataString(targetUsername)}");
        usersResp.EnsureSuccessStatusCode();

        using var usersDoc = await System.Text.Json.JsonDocument.ParseAsync(
                                 await usersResp.Content.ReadAsStreamAsync());
        foreach (var user in usersDoc.RootElement.EnumerateArray())
        {
            var name = user.GetProperty("userName").GetString();
            if (string.Equals(name, targetUsername, StringComparison.OrdinalIgnoreCase))
                return user.GetProperty("userId").GetInt32();
        }

        throw new InvalidOperationException(
            $"Xibo user '{targetUsername}' not found. Post-instance init may not have completed yet.");
    }
}

/// <summary>Snapshot of provisioned credentials for a CMS instance.</summary>
public class InstanceCredentials
{
    public string  AdminUsername     { get; set; } = string.Empty;
    public string? AdminPassword     { get; set; }
    public string? OAuthClientId     { get; set; }
    public string? OAuthClientSecret { get; set; }

    public bool HasAdminPassword     => !string.IsNullOrWhiteSpace(AdminPassword);
    public bool HasOAuthCredentials  => !string.IsNullOrWhiteSpace(OAuthClientId) &&
                                        !string.IsNullOrWhiteSpace(OAuthClientSecret);
}
