<?php

namespace App\Models;

use App\Enums\EmailConfirmationType;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

#[Fillable([
    'type',
    'user_id',
    'withdrawal_request_id',
    'payload',
    'token_hash',
    'expires_at',
    'used_at',
])]
class EmailConfirmationToken extends Model
{
    /**
     * @return BelongsTo<User, EmailConfirmationToken>
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * @return BelongsTo<WithdrawalRequest, EmailConfirmationToken>
     */
    public function withdrawalRequest(): BelongsTo
    {
        return $this->belongsTo(WithdrawalRequest::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'payload' => 'array',
            'expires_at' => 'datetime',
            'used_at' => 'datetime',
        ];
    }

    public function typeEnum(): EmailConfirmationType
    {
        /** @var EmailConfirmationType $enum */
        $enum = EmailConfirmationType::from($this->type);

        return $enum;
    }

    public function isValid(): bool
    {
        return $this->used_at === null && $this->expires_at->isFuture();
    }

    public function markUsed(): void
    {
        $this->forceFill(['used_at' => now()])->save();
    }

    public static function hashPlainToken(string $plain): string
    {
        return hash('sha256', $plain);
    }

    /**
     * @param  array<string, mixed>|null  $payload
     * @return array{plain: string, token: EmailConfirmationToken}
     */
    public static function mint(EmailConfirmationType $type, ?int $userId, ?int $withdrawalRequestId, ?array $payload, ?CarbonInterface $expiresAt = null): array
    {
        $plain = bin2hex(random_bytes(32));

        $token = static::query()->create([
            'type' => $type->value,
            'user_id' => $userId,
            'withdrawal_request_id' => $withdrawalRequestId,
            'payload' => $payload,
            'token_hash' => static::hashPlainToken($plain),
            'expires_at' => ($expiresAt ?? now()->addHours(48)),
        ]);

        return ['plain' => $plain, 'token' => $token];
    }

    public static function findValidByPlain(string $plain): ?self
    {
        $hash = static::hashPlainToken($plain);

        /** @var self|null $row */
        $row = static::query()
            ->where('token_hash', $hash)
            ->whereNull('used_at')
            ->where('expires_at', '>', now())
            ->first();

        return $row;
    }
}
