<?php

namespace apexl\Io\modules\user\controllers;

use apexl\hashing\Hash;
use apexl\Io\exceptions\RecordNotFoundException;
use apexl\Io\includes\Controller;
use apexl\Io\includes\RouteManager;
use apexl\Io\modules\user\entities\PasswordReset;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\exceptions\ForgotPasswordLinkExpiredException;
use apexl\Io\modules\user\Request\ResetPasswordRequest;
use apexl\Io\services\HttpPaths;
use JsonException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final readonly class ResetPasswordController extends Controller
{
    /**
     * @throws JsonException
     */
    public function __invoke(
        ResetPasswordRequest $request,
        ResponseInterface $response,
        HttpPaths $path,
        Hash $hash,
        RouteManager $routeManager,
    ): ResponseInterface {
        try {
            $reset = PasswordReset::fromHash($request->hash());

            if (!$reset->isUsable()) {
                return $this->expiredLink($request, $response, $path);
            }

            $reset->markUsed();

            $user = userEntity::fromEmail($reset->email);

            // Set the plaintext password and let the HashUserPasswordBeforeStore
            // hook hash it with password_hash() -- the same path registration
            // (UserCreateAction) uses and that login's password_verify() checks.
            // Manually hashing here (apexl\hashing\Hash, hash + salt) was then
            // re-hashed by the store hook, so the stored value never matched on
            // login: reset appeared to succeed but the user could never sign in.
            $user->password = $request->password();
            $user->store();

            $this->output->addRedirect(
                'user.reset_password.validation.success.redirect',
                $routeManager->getRoutePattern('user.display.reset-password-success')
            );
            return $this->json($response);

        } catch (RecordNotFoundException) {
            $this->output->addMessage(
                $path->getRouteName($request) . '.validation',
                'error',
                'We were unable to reset your password'
            );

            return $this->throw404($response);
        } catch (ForgotPasswordLinkExpiredException $e) {
            return $this->expiredLink($request, $response, $path);
        }
    }

    /**
     * @throws JsonException
     */
    private function expiredLink(ServerRequestInterface $request, ResponseInterface $response, HttpPaths $path): ResponseInterface
    {
        $this->output->addMetadata(
            'user.reset_password.validation.failed.refresh',
            'refreshGlobal',
            true,
        );

        $this->output->addMessage(
            $path->getRouteName($request) . '.validation',
            'error',
            'Link expired'
        );

        return $this->json($response, status: 401);
    }
}
