<?php

namespace Mnv\Modules\Payment\Paybox;

class PG_Signature
{

    /**
     * Get script name from URL (for use as parameter in self::make, self::check, etc.)
     * Получить имя скрипта из URL (для использования в качестве параметра в self::make, self::check и т.д.)
     *
     * @param string $url
     * @return string
     */
    public static function getScriptNameFromUrl(string $url ): string
    {
        $path = parse_url($url, PHP_URL_PATH);
        $len  = strlen($path);
        if ( $len == 0  ||  '/' == $path[$len-1]) {
            return "";
        }
        return basename($path);
    }

    /**
     * Get name of currently executed script (need to check signature of incoming message using self::check)
     * Получить имя текущего выполняемого скрипта (необходимо проверить подпись входящего сообщения с помощью self::check)
     *
     * @return string
     */
    public static function getOurScriptName (): string
    {
        return self::getScriptNameFromUrl( $_SERVER['PHP_SELF'] );
    }

    /**
     * Создает подпись
     *
     * @param array $arrParams  associative array of parameters for the signature / ассоциативный массив параметров для подписи
     * @param string $strSecretKey
     * @return string
     */
    public static function make (string $strScriptName, array $arrParams, string $strSecretKey ): string
    {
        $arrFlatParams = self::makeFlatParamsArray($arrParams);
        return md5( self::makeSigStr($strScriptName, $arrFlatParams, $strSecretKey) );
    }

    /**
     * Проверяет подпись
     *
     * @param string $signature
     * @param array $arrParams  associative array of parameters for the signature / ассоциативный массив параметров для подписи
     * @param string $strSecretKey
     * @return bool
     */
    public static function check (string $signature, string $strScriptName, array $arrParams, string $strSecretKey ): bool
    {
        return $signature === self::make($strScriptName, $arrParams, $strSecretKey);
    }


    /**
     * Returns a string, a hash of which coincide with the result of the make() method.
     * WARNING: This method can be used only for debugging purposes!
     *
     * Возвращает строку, хэш которой совпадает с результатом метода make().
     * ВНИМАНИЕ: Этот метод можно использовать только для целей отладки!
     *
     * @param array $arrParams  associative array of parameters for the signature
     * @param string $strSecretKey
     * @return string
     */
    static function debug_only_SigStr (string $strScriptName, array $arrParams, string $strSecretKey ): string
    {
        return self::makeSigStr($strScriptName, $arrParams, $strSecretKey);
    }


    /**
     * @param string $strScriptName
     * @param array $arrParams
     * @param string $strSecretKey
     * @return string
     */
    private static function makeSigStr (string $strScriptName, array $arrParams, string $strSecretKey ): string
    {
        unset($arrParams['pg_sig']);

        ksort($arrParams);

        array_unshift($arrParams, $strScriptName);
        array_push   ($arrParams, $strSecretKey);

        return join(';', $arrParams);
    }

    /**
     * @param array $arrParams
     * @param string $parent_name
     * @return array|string[]
     */
    private static function makeFlatParamsArray (array $arrParams, string $parent_name = '' ): array
    {
        $arrFlatParams = array();
        $i = 0;
        foreach ( $arrParams as $key => $val ) {

            $i++;
            if ( 'pg_sig' == $key )
                continue;

            /**
             * Имя делаем вида tag001subtag001
             * Чтобы можно было потом нормально отсортировать и вложенные узлы не запутались при сортировке
             */
            $name = $parent_name . $key . sprintf('%03d', $i);

            if (is_array($val) ) {
                $arrFlatParams = array_merge($arrFlatParams, self::makeFlatParamsArray($val, $name));
                continue;
            }

            $arrFlatParams += array($name => (string)$val);
        }

        return $arrFlatParams;
    }

    /********************** singing XML ***********************/

    /**
     * make the signature for XML
     * создайте подпись для XML
     *
     * @param string|SimpleXMLElement $xml
     * @param string $strSecretKey
     * @return string
     */
    public static function makeXML (string $strScriptName, string $xml, string $strSecretKey ): string
    {
        $arrFlatParams = self::makeFlatParamsXML($xml);
        return self::make($strScriptName, $arrFlatParams, $strSecretKey);
    }

    /**
     * Verifies the signature of XML
     * Проверяет подпись XML
     *
     * @param string|SimpleXMLElement $xml
     * @param string $strSecretKey
     * @return bool
     */
    public static function checkXML (string $strScriptName, string $xml, string $strSecretKey ): bool
    {
        if ( ! $xml instanceof SimpleXMLElement ) {
            $xml = new SimpleXMLElement($xml);
        }
        $arrFlatParams = self::makeFlatParamsXML($xml);
        return self::check((string)$xml->pg_sig, $strScriptName, $arrFlatParams, $strSecretKey);
    }

    /**
     * Returns a string, a hash of which coincide with the result of the makeXML() method.
     * WARNING: This method can be used only for debugging purposes!
     *
     * Возвращает строку, хэш которой совпадает с результатом метода make XML().
     * ВНИМАНИЕ: Этот метод можно использовать только для целей отладки!
     *
     * @param string|SimpleXMLElement $xml
     * @param string $strSecretKey
     * @return string
     */
    public static function debug_only_SigStrXML (string $strScriptName, string $xml, string $strSecretKey ): string
    {
        $arrFlatParams = self::makeFlatParamsXML($xml);
        return self::makeSigStr($strScriptName, $arrFlatParams, $strSecretKey);
    }

    /**
     * Возвращает плоский массив параметров XML
     *
     * @param (string|SimpleXMLElement) $xml
     * @return array
     * @throws Exception
     */
    private static function makeFlatParamsXML ( $xml, string $parent_name = '' ): array
    {
        if ( ! $xml instanceof SimpleXMLElement ) {
            $xml = new SimpleXMLElement($xml);
        }

        $arrParams = array();
        $i = 0;
        foreach ( $xml->children() as $tag ) {

            $i++;
            if ( 'pg_sig' == $tag->getName() )
                continue;

            /**
             * Имя делаем вида tag001subtag001
             * Чтобы можно было потом нормально отсортировать и вложенные узлы не запутались при сортировке
             */
            $name = $parent_name . $tag->getName().sprintf('%03d', $i);

            if ( $tag->children()->count() > 0) {
                $arrParams = array_merge($arrParams, self::makeFlatParamsXML($tag, $name));
                continue;
            }

            $arrParams += array($name => (string)$tag);
        }

        return $arrParams;
    }
}