using ChessCubing.Server.Data;
using Microsoft.Extensions.Options;
using MySqlConnector;

namespace ChessCubing.Server.Users;

public sealed class MySqlUserProfileStore(
    IOptions<SiteDataOptions> options,
    ILogger<MySqlUserProfileStore> logger)
{
    private const string CreateTableSql = """
        CREATE TABLE IF NOT EXISTS site_users (
            id BIGINT NOT NULL AUTO_INCREMENT,
            subject VARCHAR(190) NOT NULL,
            username VARCHAR(120) NOT NULL,
            email VARCHAR(255) NULL,
            display_name VARCHAR(120) NOT NULL,
            club VARCHAR(120) NULL,
            city VARCHAR(120) NULL,
            preferred_format VARCHAR(40) NULL,
            favorite_cube VARCHAR(120) NULL,
            bio TEXT NULL,
            created_utc DATETIME(6) NOT NULL,
            updated_utc DATETIME(6) NOT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY uq_site_users_subject (subject)
        ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
        """;

    private const string UpsertIdentitySql = """
        INSERT INTO site_users (
            subject,
            username,
            email,
            display_name,
            created_utc,
            updated_utc
        )
        VALUES (
            @subject,
            @username,
            @email,
            @displayName,
            @createdUtc,
            @updatedUtc
        )
        ON DUPLICATE KEY UPDATE
            username = VALUES(username),
            email = VALUES(email);
        """;

    private const string UpsertProfileSql = """
        INSERT INTO site_users (
            subject,
            username,
            email,
            display_name,
            club,
            city,
            preferred_format,
            favorite_cube,
            bio,
            created_utc,
            updated_utc
        )
        VALUES (
            @subject,
            @username,
            @email,
            @displayName,
            @club,
            @city,
            @preferredFormat,
            @favoriteCube,
            @bio,
            @createdUtc,
            @updatedUtc
        )
        ON DUPLICATE KEY UPDATE
            username = VALUES(username),
            email = VALUES(email),
            display_name = VALUES(display_name),
            club = VALUES(club),
            city = VALUES(city),
            preferred_format = VALUES(preferred_format),
            favorite_cube = VALUES(favorite_cube),
            bio = VALUES(bio),
            updated_utc = VALUES(updated_utc);
        """;

    private const string SelectProfileSql = """
        SELECT
            subject,
            username,
            email,
            display_name,
            club,
            city,
            preferred_format,
            favorite_cube,
            bio,
            created_utc,
            updated_utc
        FROM site_users
        WHERE subject = @subject
        LIMIT 1;
        """;

    private readonly SiteDataOptions _options = options.Value;
    private readonly ILogger<MySqlUserProfileStore> _logger = logger;

    public async Task InitializeAsync(CancellationToken cancellationToken)
    {
        for (var attempt = 1; attempt <= _options.InitializationRetries; attempt++)
        {
            try
            {
                await using var connection = new MySqlConnection(_options.BuildConnectionString());
                await connection.OpenAsync(cancellationToken);

                await using var command = connection.CreateCommand();
                command.CommandText = CreateTableSql;
                await command.ExecuteNonQueryAsync(cancellationToken);
                return;
            }
            catch (Exception exception) when (attempt < _options.InitializationRetries)
            {
                _logger.LogWarning(
                    exception,
                    "Initialisation MySQL impossible pour le profil utilisateur (tentative {Attempt}/{MaxAttempts}).",
                    attempt,
                    _options.InitializationRetries);

                await Task.Delay(TimeSpan.FromSeconds(_options.InitializationDelaySeconds), cancellationToken);
            }
        }

        await using var finalConnection = new MySqlConnection(_options.BuildConnectionString());
        await finalConnection.OpenAsync(cancellationToken);

        await using var finalCommand = finalConnection.CreateCommand();
        finalCommand.CommandText = CreateTableSql;
        await finalCommand.ExecuteNonQueryAsync(cancellationToken);
    }

    public async Task<UserProfileResponse> GetOrCreateAsync(
        AuthenticatedSiteUser user,
        CancellationToken cancellationToken)
    {
        await using var connection = new MySqlConnection(_options.BuildConnectionString());
        await connection.OpenAsync(cancellationToken);

        var nowUtc = DateTime.UtcNow;

        await using (var command = connection.CreateCommand())
        {
            command.CommandText = UpsertIdentitySql;
            command.Parameters.AddWithValue("@subject", user.Subject);
            command.Parameters.AddWithValue("@username", user.Username);
            command.Parameters.AddWithValue("@email", (object?)user.Email ?? DBNull.Value);
            command.Parameters.AddWithValue("@displayName", user.DisplayName);
            command.Parameters.AddWithValue("@createdUtc", nowUtc);
            command.Parameters.AddWithValue("@updatedUtc", nowUtc);
            await command.ExecuteNonQueryAsync(cancellationToken);
        }

        return await ReadProfileAsync(connection, user.Subject, cancellationToken);
    }

    public async Task<UserProfileResponse> UpdateAsync(
        AuthenticatedSiteUser user,
        UpdateUserProfileRequest request,
        CancellationToken cancellationToken)
    {
        var input = NormalizeInput(user, request);
        await using var connection = new MySqlConnection(_options.BuildConnectionString());
        await connection.OpenAsync(cancellationToken);

        var nowUtc = DateTime.UtcNow;

        await using (var command = connection.CreateCommand())
        {
            command.CommandText = UpsertProfileSql;
            command.Parameters.AddWithValue("@subject", user.Subject);
            command.Parameters.AddWithValue("@username", user.Username);
            command.Parameters.AddWithValue("@email", (object?)user.Email ?? DBNull.Value);
            command.Parameters.AddWithValue("@displayName", input.DisplayName);
            command.Parameters.AddWithValue("@club", (object?)input.Club ?? DBNull.Value);
            command.Parameters.AddWithValue("@city", (object?)input.City ?? DBNull.Value);
            command.Parameters.AddWithValue("@preferredFormat", (object?)input.PreferredFormat ?? DBNull.Value);
            command.Parameters.AddWithValue("@favoriteCube", (object?)input.FavoriteCube ?? DBNull.Value);
            command.Parameters.AddWithValue("@bio", (object?)input.Bio ?? DBNull.Value);
            command.Parameters.AddWithValue("@createdUtc", nowUtc);
            command.Parameters.AddWithValue("@updatedUtc", nowUtc);
            await command.ExecuteNonQueryAsync(cancellationToken);
        }

        return await ReadProfileAsync(connection, user.Subject, cancellationToken);
    }

    private static UserProfileInput NormalizeInput(AuthenticatedSiteUser user, UpdateUserProfileRequest request)
    {
        var displayName = NormalizeOptionalValue(request.DisplayName, "nom affiche", 120) ?? user.DisplayName;
        var club = NormalizeOptionalValue(request.Club, "club", 120);
        var city = NormalizeOptionalValue(request.City, "ville", 120);
        var favoriteCube = NormalizeOptionalValue(request.FavoriteCube, "cube favori", 120);
        var bio = NormalizeOptionalValue(request.Bio, "bio", 1200);
        var preferredFormat = NormalizePreferredFormat(request.PreferredFormat);

        return new UserProfileInput(displayName, club, city, preferredFormat, favoriteCube, bio);
    }

    private static string? NormalizeOptionalValue(string? value, string fieldName, int maxLength)
    {
        var trimmed = value?.Trim();
        if (string.IsNullOrWhiteSpace(trimmed))
        {
            return null;
        }

        if (trimmed.Length > maxLength)
        {
            throw new UserProfileValidationException($"Le champ {fieldName} depasse {maxLength} caracteres.");
        }

        return trimmed;
    }

    private static string? NormalizePreferredFormat(string? value)
    {
        var normalized = NormalizeOptionalValue(value, "format prefere", 40);
        if (normalized is null)
        {
            return null;
        }

        return normalized switch
        {
            "Twice" => "Twice",
            "Time" => "Time",
            "Les deux" => "Les deux",
            _ => throw new UserProfileValidationException("Le format prefere doit etre Twice, Time ou Les deux."),
        };
    }

    private static async Task<UserProfileResponse> ReadProfileAsync(
        MySqlConnection connection,
        string subject,
        CancellationToken cancellationToken)
    {
        await using var command = connection.CreateCommand();
        command.CommandText = SelectProfileSql;
        command.Parameters.AddWithValue("@subject", subject);

        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
        if (!await reader.ReadAsync(cancellationToken))
        {
            throw new InvalidOperationException("Le profil utilisateur n'a pas pu etre charge.");
        }

        var subjectOrdinal = reader.GetOrdinal("subject");
        var usernameOrdinal = reader.GetOrdinal("username");
        var emailOrdinal = reader.GetOrdinal("email");
        var displayNameOrdinal = reader.GetOrdinal("display_name");
        var clubOrdinal = reader.GetOrdinal("club");
        var cityOrdinal = reader.GetOrdinal("city");
        var preferredFormatOrdinal = reader.GetOrdinal("preferred_format");
        var favoriteCubeOrdinal = reader.GetOrdinal("favorite_cube");
        var bioOrdinal = reader.GetOrdinal("bio");
        var createdUtcOrdinal = reader.GetOrdinal("created_utc");
        var updatedUtcOrdinal = reader.GetOrdinal("updated_utc");

        return new UserProfileResponse
        {
            Subject = reader.GetString(subjectOrdinal),
            Username = reader.GetString(usernameOrdinal),
            Email = reader.IsDBNull(emailOrdinal) ? null : reader.GetString(emailOrdinal),
            DisplayName = reader.GetString(displayNameOrdinal),
            Club = reader.IsDBNull(clubOrdinal) ? null : reader.GetString(clubOrdinal),
            City = reader.IsDBNull(cityOrdinal) ? null : reader.GetString(cityOrdinal),
            PreferredFormat = reader.IsDBNull(preferredFormatOrdinal) ? null : reader.GetString(preferredFormatOrdinal),
            FavoriteCube = reader.IsDBNull(favoriteCubeOrdinal) ? null : reader.GetString(favoriteCubeOrdinal),
            Bio = reader.IsDBNull(bioOrdinal) ? null : reader.GetString(bioOrdinal),
            CreatedUtc = DateTime.SpecifyKind(reader.GetDateTime(createdUtcOrdinal), DateTimeKind.Utc),
            UpdatedUtc = DateTime.SpecifyKind(reader.GetDateTime(updatedUtcOrdinal), DateTimeKind.Utc),
        };
    }

    private sealed record UserProfileInput(
        string DisplayName,
        string? Club,
        string? City,
        string? PreferredFormat,
        string? FavoriteCube,
        string? Bio);
}
