using System.Reflection;
using System.Text;
using Npgsql;
using TaxBaik.Domain.Interfaces;

namespace TaxBaik.Infrastructure.Data;

public class MigrationRunner
{
    private readonly string _connectionString;
    private readonly IDbConnectionFactory _connectionFactory;

    public MigrationRunner(string connectionString, IDbConnectionFactory connectionFactory)
    {
        _connectionString = connectionString;
        _connectionFactory = connectionFactory;
    }

    public async Task RunAsync()
    {
        await EnsureMigrationTableAsync();
        await ExecutePendingMigrationsAsync();
    }

    private async Task EnsureMigrationTableAsync()
    {
        using var conn = new NpgsqlConnection(_connectionString);
        await conn.OpenAsync();

        using var cmd = conn.CreateCommand();
        cmd.CommandText = @"
            CREATE TABLE IF NOT EXISTS schema_migrations (
                version VARCHAR(50) PRIMARY KEY,
                description VARCHAR(500),
                installed_on TIMESTAMPTZ DEFAULT NOW()
            );";
        await cmd.ExecuteNonQueryAsync();
    }

    private async Task ExecutePendingMigrationsAsync()
    {
        var executedMigrations = await GetExecutedMigrationsAsync();
        var migrations = GetAvailableMigrations();

        foreach (var migration in migrations.OrderBy(x => x.Version))
        {
            if (!executedMigrations.Contains(migration.Version))
            {
                await ExecuteMigrationAsync(migration);
            }
        }
    }

    private async Task<HashSet<string>> GetExecutedMigrationsAsync()
    {
        var executed = new HashSet<string>();
        using var conn = new NpgsqlConnection(_connectionString);
        await conn.OpenAsync();

        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT version FROM schema_migrations ORDER BY version;";

        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            executed.Add(reader.GetString(0));
        }

        return executed;
    }

    private List<Migration> GetAvailableMigrations()
    {
        var migrations = new List<Migration>();

        // Try file system first (for deployment), then embedded resources
        var migrationDirs = new[]
        {
            "./migrations",                           // relative
            "/home/kjh2064/taxbaik_active/migrations" // deployment
        };

        var migrationPath = migrationDirs.FirstOrDefault(Directory.Exists);

        if (migrationPath != null && Directory.Exists(migrationPath))
        {
            var files = Directory.GetFiles(migrationPath, "V*.sql").OrderBy(x => x);
            foreach (var file in files)
            {
                var fileName = Path.GetFileNameWithoutExtension(file);
                if (fileName.StartsWith("V"))
                {
                    var version = fileName.Substring(1, fileName.IndexOf('_') - 1);
                    var description = fileName.Substring(fileName.IndexOf('_') + 2);
                    var sql = File.ReadAllText(file);

                    migrations.Add(new Migration { Version = version, Description = description, Sql = sql });
                }
            }
        }
        else
        {
            var assembly = Assembly.GetExecutingAssembly();
            var resourceNames = assembly.GetManifestResourceNames()
                .Where(x => x.Contains(".Migrations.V") && x.EndsWith(".sql", StringComparison.OrdinalIgnoreCase))
                .OrderBy(x => x);

            foreach (var resourceName in resourceNames)
            {
                using var stream = assembly.GetManifestResourceStream(resourceName);
                if (stream == null)
                    continue;

                using var reader = new StreamReader(stream);
                var sql = reader.ReadToEnd();
                var fileName = Path.GetFileNameWithoutExtension(resourceName);
                var versionStart = fileName.IndexOf('V');
                var versionEnd = fileName.IndexOf('_', versionStart + 1);
                if (versionStart < 0 || versionEnd < 0)
                    continue;

                var version = fileName.Substring(versionStart + 1, versionEnd - versionStart - 1);
                var description = fileName.Substring(versionEnd + 1);

                migrations.Add(new Migration { Version = version, Description = description, Sql = sql });
            }
        }

        return migrations;
    }

    private async Task ExecuteMigrationAsync(Migration migration)
    {
        using var conn = new NpgsqlConnection(_connectionString);
        await conn.OpenAsync();

        try
        {
            using var cmd = conn.CreateCommand();
            cmd.CommandText = migration.Sql;
            await cmd.ExecuteNonQueryAsync();

            using var insertCmd = conn.CreateCommand();
            insertCmd.CommandText =
                "INSERT INTO schema_migrations (version, description) VALUES (@version, @description);";
            insertCmd.Parameters.AddWithValue("@version", migration.Version);
            insertCmd.Parameters.AddWithValue("@description", migration.Description);
            await insertCmd.ExecuteNonQueryAsync();

            Console.WriteLine($"✓ Migration {migration.Version} executed");
        }
        catch (Npgsql.PostgresException pgEx) when (pgEx.SqlState == "42P07") // relation already exists
        {
            // Already executed previously; mark as done
            Console.WriteLine($"ℹ Migration {migration.Version} already applied");
            using var insertCmd = conn.CreateCommand();
            insertCmd.CommandText =
                "INSERT INTO schema_migrations (version, description) VALUES (@version, @description) ON CONFLICT (version) DO NOTHING;";
            insertCmd.Parameters.AddWithValue("@version", migration.Version);
            insertCmd.Parameters.AddWithValue("@description", migration.Description);
            await insertCmd.ExecuteNonQueryAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"✗ Migration {migration.Version} failed: {ex.Message}");
            throw;
        }
    }

    private class Migration
    {
        public required string Version { get; set; }
        public required string Description { get; set; }
        public required string Sql { get; set; }
    }
}
