using System.Data.Common;
using Eis.Infrastructure.Data;

namespace Eis.Infrastructure.Administration;

internal sealed class AdminWorkflowRepository(IRelationalConnectionFactory connectionFactory)
{
    public Task TransferAsync(
        AccountWorkflowInstance instance,
        AccountWorkflowAction action,
        AdminAuditEntry audit,
        CancellationToken cancellationToken) => ExecuteAsync(
            instance,
            action,
            null,
            audit,
            cancellationToken);

    public Task SuperviseAsync(
        AccountWorkflowInstance instance,
        AccountWorkflowAction action,
        WorkflowBusinessReset? business,
        AdminAuditEntry audit,
        CancellationToken cancellationToken) => ExecuteAsync(
            instance,
            action,
            business,
            audit,
            cancellationToken);

    private async Task ExecuteAsync(
        AccountWorkflowInstance instance,
        AccountWorkflowAction action,
        WorkflowBusinessReset? business,
        AdminAuditEntry audit,
        CancellationToken cancellationToken)
    {
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
        try
        {
            await ExecuteAsync(connection, transaction, new SqlOperation(
                """
                UPDATE workflow_instances SET status = @status, current_step = @currentStep,
                  assignee_id = @assigneeId, completed_at = @completedAt WHERE id = @id
                """,
                [
                    new("@status", instance.Status), new("@currentStep", instance.CurrentStep),
                    new("@assigneeId", instance.AssigneeId), new("@completedAt", instance.CompletedAt),
                    new("@id", instance.Id)
                ]), cancellationToken);
            await ExecuteAsync(connection, transaction, new SqlOperation(
                """
                INSERT INTO workflow_actions (
                  id, instance_id, actor_id, action, note, from_assignee_id, to_assignee_id, created_at
                ) VALUES (
                  @id, @instanceId, @actorId, @action, @note, @fromAssigneeId, @toAssigneeId, @createdAt
                )
                """,
                [
                    new("@id", action.Id), new("@instanceId", action.InstanceId), new("@actorId", action.ActorId),
                    new("@action", action.Action), new("@note", action.Note), new("@fromAssigneeId", action.FromAssigneeId),
                    new("@toAssigneeId", action.ToAssigneeId), new("@createdAt", action.CreatedAt)
                ]), cancellationToken);
            if (business is not null)
            {
                await ExecuteBusinessResetAsync(connection, transaction, business, cancellationToken);
            }
            await ExecuteAsync(connection, transaction, new SqlOperation(
                """
                INSERT INTO audit_logs (id, actor_id, action, detail, created_at)
                VALUES (@id, @actorId, @action, @detail, @createdAt)
                """,
                [
                    new("@id", audit.Id), new("@actorId", audit.ActorId), new("@action", audit.Action),
                    new("@detail", audit.Detail), new("@createdAt", audit.CreatedAt)
                ]), cancellationToken);
            await transaction.CommitAsync(cancellationToken);
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }

    private static Task ExecuteBusinessResetAsync(
        DbConnection connection,
        DbTransaction transaction,
        WorkflowBusinessReset business,
        CancellationToken cancellationToken)
    {
        var operation = business.BusinessType switch
        {
            "profile_change" => new SqlOperation(
                """
                UPDATE candidate_profiles SET status = 'pending', review_note = @note,
                  reviewed_at = NULL, reviewer_id = NULL WHERE id = @id
                """,
                [new("@note", Optional(business.Note)), new("@id", business.BusinessId)]),
            "registration_review" => new SqlOperation(
                "UPDATE registrations SET status = 'pending', review_note = @note, reviewed_at = NULL WHERE id = @id",
                [new("@note", Optional(business.Note)), new("@id", business.BusinessId)]),
            "center_change" => new SqlOperation(
                "UPDATE center_change_requests SET status = 'pending', review_note = @note, reviewed_at = NULL WHERE id = @id",
                [new("@note", Optional(business.Note)), new("@id", business.BusinessId)]),
            "candidate_account_batch" => new SqlOperation(
                "UPDATE candidate_account_batches SET status = 'pending', review_note = @note, reviewed_at = NULL WHERE id = @id",
                [new("@note", Optional(business.Note)), new("@id", business.BusinessId)]),
            _ => throw new InvalidOperationException($"Unsupported workflow business type: {business.BusinessType}")
        };
        return ExecuteAsync(connection, transaction, operation, cancellationToken);
    }

    private static async Task ExecuteAsync(
        DbConnection connection,
        DbTransaction transaction,
        SqlOperation operation,
        CancellationToken cancellationToken)
    {
        await using var command = connection.CreateCommand();
        command.Transaction = transaction;
        command.CommandText = operation.Sql;
        foreach (var item in operation.Parameters)
        {
            var parameter = command.CreateParameter();
            parameter.ParameterName = item.Name;
            parameter.Value = item.Value ?? DBNull.Value;
            command.Parameters.Add(parameter);
        }
        await command.ExecuteNonQueryAsync(cancellationToken);
    }

    private static string? Optional(string value) => value.Length == 0 ? null : value;
    private sealed record SqlOperation(string Sql, IReadOnlyList<SqlParameterValue> Parameters);
    private sealed record SqlParameterValue(string Name, object? Value);
}

internal sealed record WorkflowBusinessReset(string BusinessType, string BusinessId, string Note);
