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

namespace Eis.Infrastructure.Administration;

internal sealed class AdminConfigurationRepository(IRelationalConnectionFactory connectionFactory)
{
    public Task SaveNumberRuleAsync(AccountNumberRule rule, bool isNew, AdminAuditEntry audit, CancellationToken cancellationToken)
    {
        var operations = new List<SqlOperation>
        {
            new("UPDATE number_rules SET active = 0 WHERE active = 1", [])
        };
        if (isNew)
        {
            operations.Add(new("""
                INSERT INTO number_rules (id, name, `separator`, active, created_by, updated_at)
                VALUES (@id, @name, @separator, @active, @createdBy, @updatedAt)
                """, Parameters(rule)));
        }
        else
        {
            operations.Add(new("""
                UPDATE number_rules SET name = @name, `separator` = @separator, active = @active,
                  created_by = @createdBy, updated_at = @updatedAt WHERE id = @id
                """, Parameters(rule)));
            operations.Add(new("DELETE FROM number_rule_segments WHERE rule_id = @id", [new("@id", rule.Id)]));
        }
        operations.AddRange(rule.Segments.Select(segment => new SqlOperation("""
            INSERT INTO number_rule_segments (id, rule_id, position, type, value, width)
            VALUES (@id, @ruleId, @position, @type, @value, @width)
            """,
            [
                new("@id", segment.Id), new("@ruleId", rule.Id), new("@position", segment.Position),
                new("@type", segment.Type), new("@value", Optional(segment.Value)), new("@width", segment.Width)
            ])));
        operations.Add(AuditOperation(audit));
        return ExecuteAsync(operations, cancellationToken);
    }

    public Task SaveWorkflowAsync(AccountWorkflow workflow, AdminAuditEntry audit, CancellationToken cancellationToken)
    {
        var operations = new List<SqlOperation>
        {
            new("""
                UPDATE workflow_definitions SET name = @name, active = @active,
                  updated_by = @updatedBy, updated_at = @updatedAt WHERE id = @id
                """,
                [
                    new("@name", workflow.Name), new("@active", workflow.Active ? 1 : 0),
                    new("@updatedBy", workflow.UpdatedBy), new("@updatedAt", workflow.UpdatedAt), new("@id", workflow.Id)
                ]),
            new("DELETE FROM workflow_steps WHERE workflow_id = @id", [new("@id", workflow.Id)])
        };
        operations.AddRange(workflow.Steps.Select(step => new SqlOperation("""
            INSERT INTO workflow_steps (id, workflow_id, position, name, admin_level)
            VALUES (@id, @workflowId, @position, @name, @adminLevel)
            """,
            [
                new("@id", step.Id), new("@workflowId", workflow.Id), new("@position", step.Position),
                new("@name", step.Name), new("@adminLevel", step.AdminLevel)
            ])));
        operations.Add(AuditOperation(audit));
        return ExecuteAsync(operations, cancellationToken);
    }

    private async Task ExecuteAsync(IReadOnlyList<SqlOperation> operations, CancellationToken cancellationToken)
    {
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
        try
        {
            foreach (var operation in operations)
            {
                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);
            }
            await transaction.CommitAsync(cancellationToken);
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }

    private static IReadOnlyList<SqlParameterValue> Parameters(AccountNumberRule rule) =>
    [
        new("@id", rule.Id), new("@name", rule.Name), new("@separator", rule.Separator),
        new("@active", rule.Active ? 1 : 0), new("@createdBy", rule.CreatedBy), new("@updatedAt", rule.UpdatedAt)
    ];

    private static SqlOperation AuditOperation(AdminAuditEntry audit) => new("""
        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)
        ]);

    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);
}
