<?php

/*
 * This file is part of the SymfonyCasts ResetPasswordBundle package.
 * Copyright (c) SymfonyCasts <https://symfonycasts.com/>
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SymfonyCasts\Bundle\ResetPassword\Model;

/**
 * @author Jesse Rushlow <jr@rushlow.dev>
 * @author Ryan Weaver   <ryan@symfonycasts.com>
 */
final class ResetPasswordToken
{
    /**
     * @var string|null selector + non-hashed verifier token
     */
    private $token;

    /**
     * @var \DateTimeInterface
     */
    private $expiresAt;

    /**
     * @var int|null timestamp when the token was created
     */
    private $generatedAt;

    /**
     * @var int expiresAt translator interval
     */
    private $transInterval = 0;

    public function __construct(string $token, \DateTimeInterface $expiresAt, ?int $generatedAt = null)
    {
        $this->token = $token;
        $this->expiresAt = $expiresAt;
        $this->generatedAt = $generatedAt;

        if (null === $generatedAt) {
            $this->triggerDeprecation();
        }
    }

    /**
     * Returns the full token the user should use.
     *
     * Internally, this consists of two parts - the selector and
     * the hashed token - but that's an implementation detail
     * of how the token will later be parsed.
     */
    public function getToken(): string
    {
        if (null === $this->token) {
            throw new \RuntimeException('The token property is not set. Calling getToken() after calling clearToken() is not allowed.');
        }

        return $this->token;
    }

    /**
     * Allow the token object to be safely persisted in a session.
     */
    public function clearToken(): void
    {
        $this->token = null;
    }

    public function getExpiresAt(): \DateTimeInterface
    {
        return $this->expiresAt;
    }

    /**
     * Get the translation message for when a token expires.
     *
     * This is used in conjunction with getExpirationMessageData() method.
     * Example usage in a Twig template:
     *
     * <p>{{ components.expirationMessageKey|trans(components.expirationMessageData) }}</p>
     *
     * symfony/translation is required to translate into a non-English locale.
     *
     * @throws \LogicException
     */
    public function getExpirationMessageKey(): string
    {
        $interval = $this->getExpiresAtIntervalInstance();

        switch ($interval) {
            case $interval->y > 0:
                $this->transInterval = $interval->y;

                return '%count% year|%count% years';
            case $interval->m > 0:
                $this->transInterval = $interval->m;

                return '%count% month|%count% months';
            case $interval->d > 0:
                $this->transInterval = $interval->d;

                return '%count% day|%count% days';
            case $interval->h > 0:
                $this->transInterval = $interval->h;

                return '%count% hour|%count% hours';
            default:
                $this->transInterval = $interval->i;

                return '%count% minute|%count% minutes';
        }
    }

    /**
     * @throws \LogicException
     */
    public function getExpirationMessageData(): array
    {
        $this->getExpirationMessageKey();

        return ['%count%' => $this->transInterval];
    }

    /**
     * Get the interval that the token is valid for.
     *
     * @throws \LogicException
     *
     * @psalm-suppress PossiblyFalseArgument
     */
    public function getExpiresAtIntervalInstance(): \DateInterval
    {
        if (null === $this->generatedAt) {
            throw new \LogicException(sprintf('%s initialized without setting the $generatedAt timestamp.', self::class));
        }

        $createdAtTime = \DateTimeImmutable::createFromFormat('U', (string) $this->generatedAt);

        return $this->expiresAt->diff($createdAtTime);
    }

    /**
     * @psalm-suppress UndefinedFunction
     */
    private function triggerDeprecation(): void
    {
        trigger_deprecation(
            'symfonycasts/reset-password-bundle',
            '1.2',
            'Initializing the %s without setting the "$generatedAt" constructor argument is deprecated. The default "null" will be removed in the future.',
            self::class
        );
    }
}
