using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;

/// <summary>
/// Provides environment-aware migration behavior for the ScadaBridge configuration database.
/// </summary>
public static class MigrationHelper
{
    /// <summary>
    /// Applies pending migrations (development mode) or validates schema version (production mode).
    /// </summary>
    /// <param name="dbContext">The database context to migrate or validate.</param>
    /// <param name="isDevelopment">When true, auto-applies migrations. When false, validates schema version matches.</param>
    /// <param name="logger">Optional logger for readiness-wait diagnostics.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public static async Task ApplyOrValidateMigrationsAsync(
        ScadaBridgeDbContext dbContext,
        bool isDevelopment,
        ILogger? logger = null,
        CancellationToken cancellationToken = default)
    {
        // Wait for the target database to accept connections before invoking MigrateAsync.
        // On a fresh MSSQL container, user databases recover asynchronously after the server
        // starts accepting connections — DB_ID(@dbName) returns null until recovery completes.
        // Without this wait, MigrateAsync sees the database as missing and falls through to
        // CREATE DATABASE, which fails for non-privileged app logins.
        await WaitForDatabaseReadyAsync(dbContext, logger, cancellationToken);

        if (isDevelopment)
        {
            await dbContext.Database.MigrateAsync(cancellationToken);
        }
        else
        {
            var pendingMigrations = await dbContext.Database.GetPendingMigrationsAsync(cancellationToken);
            var pending = pendingMigrations.ToList();
            if (pending.Count > 0)
            {
                throw new InvalidOperationException(
                    $"Database schema is out of date. {pending.Count} pending migration(s): {string.Join(", ", pending)}. " +
                    "Apply migrations using 'dotnet ef database update' or the generated SQL scripts before starting in production mode.");
            }
        }

        // Safety-net normalizer: rewrite any already-persisted List attribute values from the
        // old array-of-strings JSON form to the new native-typed form. Idempotent and never
        // aborts startup for data reasons (per-row skip + log). Safe even if both central
        // nodes run it concurrently on startup.
        await ListValueNormalizer.NormalizeAsync(dbContext, logger, cancellationToken);
    }

    private static async Task WaitForDatabaseReadyAsync(
        ScadaBridgeDbContext dbContext,
        ILogger? logger,
        CancellationToken cancellationToken)
    {
        var timeout = TimeSpan.FromSeconds(60);
        var pollInterval = TimeSpan.FromSeconds(2);
        var deadline = DateTimeOffset.UtcNow + timeout;
        var attempt = 0;
        Exception? lastException = null;

        while (DateTimeOffset.UtcNow < deadline)
        {
            attempt++;
            try
            {
                if (await dbContext.Database.CanConnectAsync(cancellationToken))
                {
                    if (attempt > 1)
                    {
                        logger?.LogInformation(
                            "Configuration database ready after {Attempt} attempt(s).", attempt);
                    }
                    return;
                }
                logger?.LogDebug(
                    "Configuration database not yet reachable (attempt {Attempt}).", attempt);
            }
            catch (Exception ex)
            {
                lastException = ex;
                logger?.LogDebug(ex,
                    "Configuration database not yet reachable (attempt {Attempt}).", attempt);
            }

            await Task.Delay(pollInterval, cancellationToken);
        }

        throw new InvalidOperationException(
            $"Configuration database not ready after {timeout.TotalSeconds:N0}s ({attempt} attempts). " +
            "Verify SQL Server is running and the configuration database exists and is attached.",
            lastException);
    }
}
