using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using Testcontainers.PostgreSql;
using Testcontainers.Redis;
using Xunit;

namespace ColaFlow.IntegrationTests.Infrastructure;

/// <summary>
/// Base class for integration tests that require PostgreSQL and Redis
/// Uses Testcontainers to spin up isolated database instances
/// </summary>
public abstract class IntegrationTestBase : IAsyncLifetime
{
    // PostgreSQL Container
    protected PostgreSqlContainer PostgresContainer { get; private set; } = null!;

    // Redis Container
    protected RedisContainer RedisContainer { get; private set; } = null!;

    // Connection Strings
    protected string PostgresConnectionString => PostgresContainer.GetConnectionString();
    protected string RedisConnectionString => RedisContainer.GetConnectionString();

    /// <summary>
    /// Initialize containers before tests
    /// Called by xUnit before any test in the class runs
    /// </summary>
    public virtual async Task InitializeAsync()
    {
        // Create PostgreSQL container
        PostgresContainer = new PostgreSqlBuilder()
            .WithImage("postgres:16-alpine")
            .WithDatabase("colaflow_test")
            .WithUsername("colaflow_test")
            .WithPassword("colaflow_test_password")
            .WithCleanUp(true)
            .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(5432))
            .Build();

        // Create Redis container
        RedisContainer = new RedisBuilder()
            .WithImage("redis:7-alpine")
            .WithCleanUp(true)
            .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
            .Build();

        // Start containers in parallel
        await Task.WhenAll(
            PostgresContainer.StartAsync(),
            RedisContainer.StartAsync()
        );

        // Optional: Run migrations or seed data
        await SeedDatabaseAsync();
    }

    /// <summary>
    /// Cleanup containers after tests
    /// Called by xUnit after all tests in the class complete
    /// </summary>
    public virtual async Task DisposeAsync()
    {
        // Stop containers in parallel
        await Task.WhenAll(
            PostgresContainer.StopAsync(),
            RedisContainer.StopAsync()
        );

        // Dispose containers
        await PostgresContainer.DisposeAsync();
        await RedisContainer.DisposeAsync();
    }

    /// <summary>
    /// Seed database with test data
    /// Override in derived classes for custom seeding
    /// </summary>
    protected virtual async Task SeedDatabaseAsync()
    {
        // Example: Create tables, seed data, etc.
        await using var connection = new NpgsqlConnection(PostgresConnectionString);
        await connection.OpenAsync();

        // Create extensions
        await using var command = connection.CreateCommand();
        command.CommandText = @"
            CREATE EXTENSION IF NOT EXISTS ""uuid-ossp"";
            CREATE EXTENSION IF NOT EXISTS ""pg_trgm"";
        ";
        await command.ExecuteNonQueryAsync();
    }

    /// <summary>
    /// Create DbContextOptions for Entity Framework Core
    /// </summary>
    protected DbContextOptions<TContext> CreateDbContextOptions<TContext>()
        where TContext : DbContext
    {
        return new DbContextOptionsBuilder<TContext>()
            .UseNpgsql(PostgresConnectionString)
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors()
            .Options;
    }

    /// <summary>
    /// Execute SQL command on test database
    /// </summary>
    protected async Task ExecuteSqlAsync(string sql)
    {
        await using var connection = new NpgsqlConnection(PostgresConnectionString);
        await connection.OpenAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        await command.ExecuteNonQueryAsync();
    }

    /// <summary>
    /// Clean database tables for test isolation
    /// </summary>
    protected async Task CleanDatabaseAsync()
    {
        await ExecuteSqlAsync(@"
            DO $$
            DECLARE
                r RECORD;
            BEGIN
                -- Disable triggers
                FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
                    EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE';
                END LOOP;
            END $$;
        ");
    }
}

/// <summary>
/// Collection fixture for sharing Testcontainers across multiple test classes
/// Use [Collection("IntegrationTests")] attribute on test classes
/// </summary>
[CollectionDefinition("IntegrationTests")]
public class IntegrationTestCollection : ICollectionFixture<IntegrationTestFixture>
{
    // This class has no code, and is never created.
    // Its purpose is simply to be the place to apply [CollectionDefinition]
}

/// <summary>
/// Shared fixture for integration tests
/// Containers are created once and shared across test classes
/// </summary>
public class IntegrationTestFixture : IAsyncLifetime
{
    public PostgreSqlContainer PostgresContainer { get; private set; } = null!;
    public RedisContainer RedisContainer { get; private set; } = null!;

    public string PostgresConnectionString => PostgresContainer.GetConnectionString();
    public string RedisConnectionString => RedisContainer.GetConnectionString();

    public async Task InitializeAsync()
    {
        // Create containers
        PostgresContainer = new PostgreSqlBuilder()
            .WithImage("postgres:16-alpine")
            .WithDatabase("colaflow_test")
            .WithUsername("colaflow_test")
            .WithPassword("colaflow_test_password")
            .WithCleanUp(true)
            .Build();

        RedisContainer = new RedisBuilder()
            .WithImage("redis:7-alpine")
            .WithCleanUp(true)
            .Build();

        // Start containers
        await Task.WhenAll(
            PostgresContainer.StartAsync(),
            RedisContainer.StartAsync()
        );
    }

    public async Task DisposeAsync()
    {
        await Task.WhenAll(
            PostgresContainer.DisposeAsync().AsTask(),
            RedisContainer.DisposeAsync().AsTask()
        );
    }
}
