using System.Data.Common;
using System.Globalization;
using System.Text.Json.Nodes;
using Eis.Infrastructure.Data;

namespace Eis.Infrastructure.Candidate;

internal sealed record CandidateAdmissionRecord(
    string Id,
    string Kind,
    string ExamId,
    string? UserId,
    string? SchoolId,
    string Status,
    JsonObject Payload,
    string CreatedAt,
    string UpdatedAt);

internal sealed record CandidateAdmissionSchool(string Id, string Code, string Name);

internal sealed record CandidateAdmissionSnapshot(
    IReadOnlyList<CandidateAdmissionRecord> Records,
    IReadOnlyDictionary<string, CandidateAdmissionSchool> Schools);

internal sealed class CandidateAdmissionRepository(IRelationalConnectionFactory connectionFactory)
{
    public async Task<CandidateAdmissionSnapshot> LoadAsync(CancellationToken cancellationToken)
    {
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        var records = new List<CandidateAdmissionRecord>();
        await using (var command = connection.CreateCommand())
        {
            command.CommandText = "SELECT id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at FROM admission_records ORDER BY created_at, id";
            await using var reader = await command.ExecuteReaderAsync(cancellationToken);
            while (await reader.ReadAsync(cancellationToken))
            {
                records.Add(new CandidateAdmissionRecord(
                    ReadString(reader, "id"),
                    ReadString(reader, "kind"),
                    ReadString(reader, "exam_id"),
                    ReadOptionalString(reader, "user_id"),
                    ReadOptionalString(reader, "school_id"),
                    ReadString(reader, "status"),
                    JsonNode.Parse(ReadString(reader, "payload_json"))?.AsObject() ?? new JsonObject(),
                    ReadString(reader, "created_at"),
                    ReadString(reader, "updated_at")));
            }
        }

        var schools = new List<CandidateAdmissionSchool>();
        await using (var command = connection.CreateCommand())
        {
            command.CommandText = "SELECT id, code, name FROM schools ORDER BY id";
            await using var reader = await command.ExecuteReaderAsync(cancellationToken);
            while (await reader.ReadAsync(cancellationToken))
            {
                schools.Add(new CandidateAdmissionSchool(
                    ReadString(reader, "id"),
                    ReadString(reader, "code"),
                    ReadString(reader, "name")));
            }
        }

        return new CandidateAdmissionSnapshot(
            records,
            schools.ToDictionary(item => item.Id, StringComparer.Ordinal));
    }

    public async Task SavePreferenceAsync(
        CandidateAdmissionRecord record,
        CancellationToken cancellationToken)
    {
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
        try
        {
            await using (var delete = connection.CreateCommand())
            {
                delete.Transaction = transaction;
                delete.CommandText = "DELETE FROM admission_records WHERE id = @id";
                AddParameter(delete, "@id", record.Id);
                await delete.ExecuteNonQueryAsync(cancellationToken);
            }

            await using (var insert = connection.CreateCommand())
            {
                insert.Transaction = transaction;
                insert.CommandText = """
                    INSERT INTO admission_records (
                      id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at
                    ) VALUES (
                      @id, @kind, @examId, @userId, @schoolId, @status, @payload, @createdAt, @updatedAt
                    )
                    """;
                AddParameter(insert, "@id", record.Id);
                AddParameter(insert, "@kind", record.Kind);
                AddParameter(insert, "@examId", record.ExamId);
                AddParameter(insert, "@userId", record.UserId);
                AddParameter(insert, "@schoolId", record.SchoolId);
                AddParameter(insert, "@status", record.Status);
                AddParameter(insert, "@payload", record.Payload.ToJsonString());
                AddParameter(insert, "@createdAt", record.CreatedAt);
                AddParameter(insert, "@updatedAt", record.UpdatedAt);
                await insert.ExecuteNonQueryAsync(cancellationToken);
            }

            await transaction.CommitAsync(cancellationToken);
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }

    private static void AddParameter(DbCommand command, string name, object? value)
    {
        var parameter = command.CreateParameter();
        parameter.ParameterName = name;
        parameter.Value = value ?? DBNull.Value;
        command.Parameters.Add(parameter);
    }

    private static string ReadString(DbDataReader reader, string name) =>
        Convert.ToString(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) ?? string.Empty;

    private static string? ReadOptionalString(DbDataReader reader, string name)
    {
        var ordinal = reader.GetOrdinal(name);
        return reader.IsDBNull(ordinal) ? null : Convert.ToString(reader.GetValue(ordinal), CultureInfo.InvariantCulture);
    }
}
