<?php

declare(strict_types=1);

namespace App\Services\Users;

use App\Enums\AuthEventType;
use App\Enums\SubscriptionStatus;
use App\Enums\UserRole;
use App\Enums\UserStatus;
use App\Models\Plan;
use App\Models\Profile;
use App\Models\Subscription;
use App\Models\User;
use App\Services\Audit\AuditEvent;
use App\Services\Audit\AuditRecorder;
use App\Services\Auth\AuthEventRecorder;
use Illuminate\Support\Facades\DB;

/**
 * Creates a marketplace account.
 *
 * One account is both buyer and seller, so registration creates no seller
 * record -- that is provisioned the first time the user actually sells. What it
 * does create is the general profile and, where one is configured, a
 * subscription to the default plan, so entitlement lookups never have to cope
 * with a user who has no plan at all.
 */
final class UserRegistrar
{
    public function __construct(
        private readonly AuditRecorder $audit,
        private readonly AuthEventRecorder $authEvents,
    ) {}

    /**
     * @param  array{name: string, email: string, password: string, phone?: string|null, locale?: string|null, timezone?: string|null, marketing_opt_in?: bool}  $data
     */
    public function register(array $data): User
    {
        /*
         * One transaction for the whole account: a user row without its profile
         * would break every screen that reads display preferences, and a
         * half-registered account is worse than a failed registration.
         */
        $user = DB::transaction(function () use ($data): User {
            $user = new User;
            $user->name = trim($data['name']);
            $user->email = mb_strtolower(trim($data['email']));
            $user->password = $data['password'];
            $user->phone = $data['phone'] ?? null;
            $user->locale = $data['locale'] ?? 'en';
            $user->timezone = $data['timezone'] ?? 'UTC';

            // Set explicitly rather than by mass assignment: role and status are
            // guarded precisely so a request body can never reach them.
            $user->role = UserRole::User;
            $user->status = UserStatus::Active;
            $user->password_changed_at = now();
            $user->save();

            $user->profile()->create([
                'display_name' => $user->name,
                'marketing_opt_in' => (bool) ($data['marketing_opt_in'] ?? false),
                'notification_preferences' => Profile::defaultNotificationPreferences(),
            ]);

            $this->attachDefaultPlan($user);

            return $user;
        });

        $this->audit->record(
            event: AuditEvent::USER_REGISTERED,
            subject: $user,
            after: ['email' => $user->email, 'name' => $user->name],
            actor: $user,
        );

        $this->authEvents->record(AuthEventType::Registered, $user);

        // Sent outside the transaction: a mail failure must not roll back a
        // successfully created account, and the user can always re-request it.
        $user->sendEmailVerificationNotification();

        return $user;
    }

    /**
     * Put the account on the default plan, if the platform defines one.
     *
     * Free plans are represented as a real subscription rather than as the
     * absence of one, so entitlement checks have a single code path.
     */
    private function attachDefaultPlan(User $user): ?Subscription
    {
        $plan = Plan::default();

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

        $subscription = new Subscription;
        $subscription->user_id = $user->getKey();
        $subscription->plan_id = $plan->getKey();
        $subscription->status = $plan->isFree()
            ? SubscriptionStatus::Active
            : SubscriptionStatus::Incomplete;
        $subscription->current_period_start = now();

        // A free plan has no period end; paid plans get theirs from the billing
        // provider once payment is confirmed.
        $subscription->current_period_end = null;
        $subscription->save();

        return $subscription;
    }
}
