<?php

namespace App\Services;

use App\Enums\AccountStatus;
use App\Models\Order;
use App\Models\Role;
use App\Models\User;
use App\Notifications\ResetPassword;
use App\Notifications\WelcomePasswordSetup;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;

/**
 * Gestiona la cuenta del comprador cuando la orden se pagó sin usuario logueado.
 *
 * Responsabilidades:
 * - Resolver qué correo identifica al comprador (`checkout_email` en la orden o asistentes).
 * - Si la orden no tiene `user_id`, enlazar un usuario existente con ese correo o crear uno rol cliente.
 * - Encolar el correo de bienvenida con enlace para establecer contraseña (solo si se creó usuario nuevo).
 *
 * Motivo: antes las compras invitado quedaban con `user_id` null y el correo del checkout no existía
 * en la tabla `users`, por lo que no había forma válida de iniciar sesión con ese email.
 */
final class GuestBuyerAccountService
{
    /**
     * Asocia la orden a un usuario o crea cuenta cliente si hace falta.
     *
     * Flujo:
     * 1. Si la orden ya tiene `user_id`, no hace nada (idempotente).
     * 2. Obtiene email del comprador con {@see resolveBuyerEmail()}; si está vacío, aborta.
     * 3. Si ya existe un usuario con ese email (comparación insensible a mayúsculas), asigna `user_id`
     *    a la orden y devuelve false (no es cuenta nueva → no enviar correo de bienvenida aquí).
     * 4. Si no existe usuario: crea uno con rol {@see Role::SLUG_CLIENTE}, contraseña aleatoria
     *    (se sustituye al usar el enlace), enlaza la orden y devuelve true.
     *
     * @return bool true si se creó un usuario nuevo en este paso (el llamador puede enviar {@see queuePasswordSetupEmail()}).
     */
    public function ensureClienteAccountLinked(Order $order): bool
    {
        $order->loadMissing('attendees');

        if ($order->user_id !== null) {
            return false;
        }

        $email = $this->resolveBuyerEmail($order);
        if ($email === '') {
            return false;
        }

        // Evitar duplicados por mayúsculas/minúsculas en el campo email de users.
        $existing = User::query()
            ->whereRaw('LOWER(email) = ?', [$email])
            ->first();

        if ($existing !== null) {
            $order->forceFill(['user_id' => $existing->id])->save();

            return false;
        }

        $roleId = Role::query()->where('slug', Role::SLUG_CLIENTE)->value('id');
        if ($roleId === null) {
            return false;
        }

        // Nombre visible: primer asistente con nombre, o parte local del email.
        $first = $order->attendees->first();
        $name = ($first && filled($first->holder_name))
            ? (string) $first->holder_name
            : Str::before($email, '@');

        // Contraseña temporal; el usuario la sustituye vía enlace (WelcomePasswordSetup).
        $user = User::create([
            'name' => $name,
            'email' => $email,
            'password' => Str::random(64),
            'role_id' => (int) $roleId,
            'account_status' => AccountStatus::Approved,
        ]);

        $order->forceFill(['user_id' => $user->id])->save();

        return true;
    }

    /**
     * Programa el envío del correo “bienvenida + establece contraseña” después del commit de BD.
     *
     * Por qué `DB::afterCommit`:
     * - Suele llamarse desde dentro de una transacción de pago; el usuario debe existir en BD
     *   antes de generar el token de reset y despachar la notificación.
     *
     * Por qué no usa `Password::sendResetLink()`:
     * - Ese método dispara {@see ResetPassword} (texto genérico de “solicitud de reset”).
     * - Aquí generamos token con el broker y enviamos {@see WelcomePasswordSetup}.
     *
     * @param  string  $email  Correo ya normalizado en minúsculas preferiblemente.
     */
    public function queuePasswordSetupEmail(string $email): void
    {
        $email = strtolower(trim($email));
        if ($email === '') {
            return;
        }

        DB::afterCommit(static function () use ($email): void {
            $user = User::query()
                ->whereRaw('LOWER(email) = ?', [$email])
                ->first();

            if ($user === null) {
                return;
            }

            $token = Password::broker()->createToken($user);
            $user->notify(new WelcomePasswordSetup($token));
        });
    }

    /**
     * Determina el correo del comprador para enlazar o crear cuenta.
     *
     * Prioridad:
     * 1. `orders.checkout_email` (capturado en el checkout para invitados).
     * 2. Primer asistente con `holder_email` no vacío (compatibilidad con órdenes antiguas).
     *
     * @return string Email en minúsculas recortado, o cadena vacía si no hay dato usable.
     */
    public function resolveBuyerEmail(Order $order): string
    {
        $raw = $order->checkout_email;
        if (is_string($raw) && trim($raw) !== '') {
            return strtolower(trim($raw));
        }

        foreach ($order->attendees as $attendee) {
            $h = $attendee->holder_email ?? '';
            if (is_string($h) && trim($h) !== '') {
                return strtolower(trim($h));
            }
        }

        return '';
    }
}
