using JSMR.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using System.Data;

namespace JSMR.Tests.Fixtures;

public static class MariaDbClone
{
    public static async Task CreateTemplateAsync(
        string rootConnectionString,
        string templateDbName,
        Func<AppDbContext, Task>? seedAsync = null)
    {
        await using MySqlConnection root = new(rootConnectionString);
        await root.OpenAsync();

        await ExecAsync(root, $"DROP DATABASE IF EXISTS `{templateDbName}`;");
        await ExecAsync(root, $"CREATE DATABASE `{templateDbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");

        var templateConn = new MySqlConnectionStringBuilder(rootConnectionString) { Database = templateDbName }.ConnectionString;
        await using var ctx = CreateAppDbContext(templateConn);
        await ctx.Database.EnsureCreatedAsync();

        if (seedAsync != null)
            await seedAsync(ctx);
    }

    public static async Task<AppDbContext> CloneFromTemplateAsync(
        string rootConnectionString,
        string templateDbName,
        Func<AppDbContext, Task>? seed = null)
    {
        var databaseName = $"t_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}";
        var connectionString = new MySqlConnectionStringBuilder(rootConnectionString) { Database = databaseName }.ConnectionString;

        await using var root = new MySqlConnection(rootConnectionString);
        await root.OpenAsync();

        // Create target DB
        await ExecAsync(root, $"CREATE DATABASE `{databaseName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");

        // Disable FK checks while recreating schema & loading data
        await ExecAsync(root, $"SET SESSION sql_log_bin = 0;"); // avoid binlog noise (optional)
        await ExecAsync(root, $"SET SESSION foreign_key_checks = 0;");

        // 1) Create tables using exact DDL from templatedb
        var tables = await GetTableNamesAsync(root, templateDbName);

        foreach (var table in tables)
        {
            var createTable = await ShowCreateTableAsync(root, templateDbName, table);
            // Run DDL in the new DB (the CREATE statement itself doesn't include db name)
            await ExecAsync(root, $"USE `{databaseName}`; {createTable};");
        }

        //await Parallel.ForEachAsync(tables, async (table, cancellationToken) =>
        //{
        //    var createTable = await ShowCreateTableAsync(root, templateDbName, table);
        //    // Run DDL in the new DB (the CREATE statement itself doesn't include db name)
        //    await ExecAsync(root, $"USE `{databaseName}`; {createTable};");
        //});

        // 2) Copy data
        foreach (var table in tables)
        {
            var sql = $"INSERT INTO `{databaseName}`.`{table}` SELECT * FROM `{templateDbName}`.`{table}`;";
            await ExecAsync(root, sql);
        }

        await ExecAsync(root, $"SET SESSION foreign_key_checks = 1;");
        await ExecAsync(root, $"SET SESSION sql_log_bin = 1;");

        AppDbContext dbContext = CreateAppDbContext(connectionString);

        if (seed != null)
            await seed(dbContext);

        return dbContext;
    }

    private static AppDbContext CreateAppDbContext(string connectionString)
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), o => o.EnableRetryOnFailure())
            .EnableSensitiveDataLogging()
            .Options;

        return new AppDbContext(options);
    }

    private static async Task<List<string>> GetTableNamesAsync(MySqlConnection conn, string db)
    {
        const string sql = @"
SELECT TABLE_NAME 
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = @db AND TABLE_TYPE = 'BASE TABLE';";

        var result = new List<string>();
        await using var cmd = new MySqlCommand(sql, conn) { Parameters = { new("@db", db) } };
        await using var rdr = await cmd.ExecuteReaderAsync();
        while (await rdr.ReadAsync())
            result.Add(rdr.GetString(0));
        return result;
    }

    private static async Task<string> ShowCreateTableAsync(MySqlConnection conn, string db, string table)
    {
        await using var cmd = new MySqlCommand($"SHOW CREATE TABLE `{db}`.`{table}`;", conn);
        await using var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow);
        if (!await rdr.ReadAsync())
            throw new InvalidOperationException($"SHOW CREATE TABLE returned no rows for {db}.{table}");

        // Column 1: table name, Column 2: CREATE TABLE DDL
        var ddl = rdr.GetString(1);

        // Ensure the statement ends with ';'
        if (!ddl.TrimEnd().EndsWith(";"))
            ddl += ";";

        return ddl;
    }

    private static async Task ExecAsync(MySqlConnection conn, string sql)
    {
        await using var cmd = new MySqlCommand(sql, conn) { CommandTimeout = 0 };
        await cmd.ExecuteNonQueryAsync();
    }
}