using Npgsql;

namespace Books.Api.Tests.Infrastructure;

/// <summary>
/// Creates an isolated PostgreSQL database for each test run.
/// The database is automatically dropped when disposed.
/// </summary>
public class TestDatabase : IDisposable
{
    public const string DefaultConnectionString =
        "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=postgres;Include Error Detail=true";

    private readonly string _masterConnectionString;
    private bool _disposed;

    public string DatabaseName { get; }

    public string ConnectionString
    {
        get
        {
            var builder = new NpgsqlConnectionStringBuilder(_masterConnectionString)
            {
                Database = DatabaseName
            };
            return builder.ToString();
        }
    }

    public TestDatabase(string? masterConnectionString = null)
    {
        _masterConnectionString = masterConnectionString ?? GetConnectionStringFromEnvironment();
        DatabaseName = $"books_test_{Guid.NewGuid():N}";

        CreateDatabase();
    }

    private static string GetConnectionStringFromEnvironment()
    {
        return Environment.GetEnvironmentVariable("TEST_CONNECTION_STRING")
            ?? DefaultConnectionString;
    }

    private void CreateDatabase()
    {
        try
        {
            using var connection = new NpgsqlConnection(_masterConnectionString);
            connection.Open();

            using var command = connection.CreateCommand();
            command.CommandText = $"CREATE DATABASE \"{DatabaseName}\"";
            command.ExecuteNonQuery();

            Console.WriteLine($"[TestDatabase] Created test database: {DatabaseName}");
        }
        catch (Exception e)
        {
            throw new InvalidOperationException(
                $"Failed to create test database '{DatabaseName}' using connection string: {_masterConnectionString}",
                e);
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            try
            {
                // Step 1: Terminate all active connections to prevent "database in use" errors
                using (var connection = new NpgsqlConnection(_masterConnectionString))
                {
                    connection.Open();

                    using var terminateCommand = connection.CreateCommand();
                    terminateCommand.CommandText = $@"
                        SELECT pg_terminate_backend(pg_stat_activity.pid)
                        FROM pg_stat_activity
                        WHERE pg_stat_activity.datname = '{DatabaseName}'
                          AND pid <> pg_backend_pid();
                    ";
                    terminateCommand.ExecuteNonQuery();
                }

                // Step 2: Drop the database
                using (var connection = new NpgsqlConnection(_masterConnectionString))
                {
                    connection.Open();

                    using var dropCommand = connection.CreateCommand();
                    dropCommand.CommandText = $"DROP DATABASE IF EXISTS \"{DatabaseName}\"";
                    dropCommand.ExecuteNonQuery();
                }

                Console.WriteLine($"[TestDatabase] Dropped test database: {DatabaseName}");
            }
            catch (Exception e)
            {
                // Log but don't throw - we don't want cleanup failures to mask test failures
                Console.WriteLine($"[TestDatabase] Warning: Failed to drop test database {DatabaseName}: {e.Message}");
            }
        }

        _disposed = true;
    }

    ~TestDatabase()
    {
        Dispose(false);
    }
}
