using System.Data;
using System.Text.RegularExpressions;
using JobTrackerApi.Data;
using JobTrackerApi.Services;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class MigrationChainTests
{
    private const string SnapshotMigration = "20260711181039_SyncModelSnapshot";

    [Fact]
    public async Task Blank_sqlite_chain_reaches_latest_and_is_idempotent()
    {
        await using var connection = new SqliteConnection("Data Source=:memory:");
        await connection.OpenAsync();
        await using var db = Context(connection);

        await db.Database.MigrateAsync();
        await db.Database.MigrateAsync();

        Assert.Empty(await db.Database.GetPendingMigrationsAsync());
        Assert.Equal(3, await ScalarAsync<long>(connection, """
            SELECT COUNT(*) FROM sqlite_master
            WHERE type = 'table' AND name IN ('AspNetUsers', 'AiInteractions', 'AiUsageRecords');
            """));
        Assert.Equal(10, await ScalarAsync<long>(connection, """
            SELECT COUNT(*) FROM pragma_table_info('JobApplications')
            WHERE name IN ('OwnerUserId', 'ShortSummary', 'TailoredCvText', 'TailoredCvUpdatedAt',
                'LastReminderEmailSentAt', 'RecruiterMessageDraft', 'SalaryMin', 'SalaryMax',
                'SalaryCurrency', 'SalaryPeriod');
            """));
        Assert.Equal(1, await ScalarAsync<long>(connection, """
            SELECT COUNT(*) FROM pragma_table_info('AspNetUsers')
            WHERE name = 'EmailFollowUpRemindersEnabled' AND dflt_value IN ('1', 'true');
            """));
    }

    [Fact]
    public async Task Populated_pre_job_split_database_preserves_rows_through_latest()
    {
        await using var connection = new SqliteConnection("Data Source=:memory:");
        await connection.OpenAsync();
        await using var db = Context(connection);
        var migrator = db.GetService<IMigrator>();
        await migrator.MigrateAsync(SnapshotMigration);
        await ExecuteAsync(connection, """
            INSERT INTO Companies (Name) VALUES ('Migration fixture');
            INSERT INTO JobApplications
                (CompanyId, JobTitle, DateApplied, Status, ResponseReceived, OwnerUserId, ShortSummary)
            VALUES
                (1, 'Preserved role', '2026-07-01 09:30:00', 'Applied', 0, 'owner-1', 'Preserved summary');
            """);

        await migrator.MigrateAsync();

        await using var command = connection.CreateCommand();
        command.CommandText = """
            SELECT JobTitle, DateApplied, SavedAt, OwnerUserId, ShortSummary
            FROM JobApplications WHERE Id = 1;
            """;
        await using var reader = await command.ExecuteReaderAsync();
        Assert.True(await reader.ReadAsync());
        Assert.Equal("Preserved role", reader.GetString(0));
        Assert.Equal(reader.GetString(1), reader.GetString(2));
        Assert.Equal("owner-1", reader.GetString(3));
        Assert.Equal("Preserved summary", reader.GetString(4));
        Assert.False(await reader.ReadAsync());
        Assert.Empty(await db.Database.GetPendingMigrationsAsync());
    }

    [Fact]
    public async Task MariaDb_script_keeps_new_identifiers_and_bootstrap_types_provider_safe()
    {
        var currentUser = new Mock<ICurrentUserService>();
        currentUser.SetupGet(service => service.UserId).Returns((string?)null);
        var options = new DbContextOptionsBuilder<JobTrackerContext>()
            .UseMySql(
                "Server=127.0.0.1;Database=script_only;User=none;Password=none;",
                new MariaDbServerVersion(new Version(11, 0, 0)))
            .Options;
        await using var db = new JobTrackerContext(options, currentUser.Object);

        var script = db.GetService<IMigrator>().GenerateScript();

        Assert.Contains("`OwnerUserId` varchar(255)", script, StringComparison.Ordinal);
        Assert.Contains("CONSTRAINT `FK_AccountDeletionFiles_Request`", script, StringComparison.Ordinal);
        Assert.Contains("CREATE TABLE IF NOT EXISTS `AspNetUsers`", script, StringComparison.Ordinal);
        Assert.Contains("CREATE TABLE IF NOT EXISTS `AiInteractions`", script, StringComparison.Ordinal);
        Assert.All(
            Regex.Matches(script, "CONSTRAINT `([^`]+)`").Select(match => match.Groups[1].Value),
            identifier => Assert.True(identifier.Length <= 64, $"MariaDB constraint identifier exceeds 64 characters: {identifier}"));
    }

    private static JobTrackerContext Context(SqliteConnection connection)
    {
        var currentUser = new Mock<ICurrentUserService>();
        currentUser.SetupGet(service => service.UserId).Returns((string?)null);
        return new JobTrackerContext(
            new DbContextOptionsBuilder<JobTrackerContext>().UseSqlite(connection).Options,
            currentUser.Object);
    }

    private static async Task<T> ScalarAsync<T>(SqliteConnection connection, string sql)
    {
        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        return (T)Convert.ChangeType(await command.ExecuteScalarAsync() ?? throw new DataException(), typeof(T));
    }

    private static async Task ExecuteAsync(SqliteConnection connection, string sql)
    {
        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        await command.ExecuteNonQueryAsync();
    }
}
