using Insight.Database;
using Npgsql;
using PlanTempus.Components.Accounts.Exceptions;
using PlanTempus.Core;
using PlanTempus.Core.CommandQueries;
using PlanTempus.Core.Database;
using PlanTempus.Core.Outbox;

namespace PlanTempus.Components.Accounts.Create
{
    public class CreateAccountHandler(
        IDatabaseOperations databaseOperations,
        ISecureTokenizer secureTokenizer,
        IOutboxService outboxService) : ICommandHandler<CreateAccountCommand>
    {
        public async Task<CommandResponse> Handle(CreateAccountCommand command)
        {
            using var db = databaseOperations.CreateScope(nameof(CreateAccountHandler));
            using var transaction = db.Connection.BeginTransaction();

            try
            {
                var securityStamp = Guid.NewGuid().ToString("N");

                var sql = @"
                    INSERT INTO system.accounts(email, password_hash, security_stamp, email_confirmed,
                     access_failed_count, lockout_enabled, is_active)
                    VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed,
                     @AccessFailedCount, @LockoutEnabled, @IsActive)
                    RETURNING id, created_at, email, is_active";

                await db.Connection.QuerySqlAsync(sql, new
                {
                    command.Email,
                    PasswordHash = secureTokenizer.TokenizeText(command.Password),
                    SecurityStamp = securityStamp,
                    EmailConfirmed = false,
                    AccessFailedCount = 0,
                    LockoutEnabled = false,
                    command.IsActive,
                });

                await outboxService.EnqueueAsync(
                    OutboxMessageTypes.VerificationEmail,
                    new VerificationEmailPayload
                    {
                        Email = command.Email,
                        UserName = command.Email,
                        Token = securityStamp
                    },
                    db.Connection,
                    transaction);

                transaction.Commit();
                return new CommandResponse(command.CorrelationId, command.GetType().Name, command.TransactionId);
            }
            catch (PostgresException ex) when (ex.SqlState == "23505" && ex.ConstraintName.Equals("accounts_email_key", StringComparison.InvariantCultureIgnoreCase))
            {
                transaction.Rollback();
                db.Error(ex);
                throw new EmailAlreadyRegistreredException();
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                db.Error(ex);
                throw;
            }
        }
    }
}
