<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

declare(strict_types=1);

namespace SimplyIn\Security;

if (!defined('_PS_VERSION_')) {
    exit;
}

class TokenService
{
    private string $moduleName;

    private string $moduleVersion;

    private string $cookieKey;

    public function __construct(string $moduleName, string $moduleVersion, string $cookieKey)
    {
        $this->moduleName = $moduleName;
        $this->moduleVersion = $moduleVersion;
        $this->cookieKey = $cookieKey;
    }

    public function getAjaxToken(): string
    {
        return $this->hmac('ajax|' . $this->moduleName . '|' . $this->moduleVersion);
    }

    public function getCartToken(mixed $idCart = false): string
    {
        $idCart = is_scalar($idCart) ? (string) $idCart : '';

        return $this->hmac('cart|' . $this->moduleName . '|' . $this->moduleVersion . '|' . $idCart);
    }

    public function getOrderToken(mixed $idCart): string
    {
        $idCart = is_scalar($idCart) ? (string) $idCart : '';

        return $this->hmac('order|' . $this->moduleName . '|' . $idCart);
    }

    /**
     * Authorizes paying an existing order again - and, since that is the stronger of the two, also
     * authorizes the strictly narrower act of merely reading that order's retry state.
     *
     * Bound to the order's own secret key, so the token can only be produced by code that already
     * loaded that order — which the shop only lets happen after its own ownership check, on the
     * logged-in and the guest-tracking route alike. That is what lets the endpoint verify the
     * caller without a session, which the guest route has none of.
     *
     * The module version is deliberately not part of the message, matching getOrderToken(): a
     * version bump must not invalidate a token on a page a shopper already has open.
     */
    public function getPaymentRetryToken(int $idOrder, string $secureKey): string
    {
        return $this->hmac('payment-retry|' . $this->moduleName . '|' . $idOrder . '|' . $secureKey);
    }

    public function getPaymentCheckToken(string $ajaxToken, string $cartToken): string
    {
        return $this->hmac('payment-check|' . $this->moduleName . '|' . $this->moduleVersion . '|' . $ajaxToken . '|' . $cartToken);
    }

    public function getCronSecureKey(): string
    {
        return $this->hmac('cron');
    }

    public function getLegacyCronSecureKey(): string
    {
        return md5($this->cookieKey . '-getCronSecureKey');
    }

    public function getAsyncToken(string $cookieIv): string
    {
        return $this->hmac('async|' . $cookieIv . '|' . $this->moduleName);
    }

    private function hmac(string $message): string
    {
        return hash_hmac('sha256', $message, $this->cookieKey);
    }
}
