<?php

declare(strict_types=1);

namespace BeycanPress\Tokenico;

use BeycanPress\Tokenico\PluginHero\Hook;
use BeycanPress\Tokenico\PluginHero\Helpers;

class Service
{
    /**
     * @param int $userId
     * @return bool
     */
    public static function isAdmin(int $userId): bool
    {
        $userData = get_userdata($userId);
        if (!$userData) {
            return false;
        }
        return in_array('administrator', $userData->roles);
    }

    /**
     * @return void
     */
    public static function createTonManifestIfNotExists(): void
    {
        $manifestPath = Helpers::getProp('pluginDir') . 'manifest.json';
        if (!file_exists($manifestPath)) {
            file_put_contents($manifestPath, json_encode([
                'name' => get_bloginfo('name'),
                'url' => get_bloginfo('url'),
                'iconUrl' => get_site_icon_url(512, Helpers::getImageUrl('icon.png'))
            ]));
        }
    }

    /**
     * @param array<mixed> $config
     * @return void
     */
    public static function initAssets(array $config = []): void
    {
        self::createTonManifestIfNotExists();

        if (Settings::get('enableCustomStyle', false)) {
            $colors = Settings::get('colors', [
                'textColor' => '#525252',
                'primaryColor' => '#4099ff',
                'secondaryColor' => '#79bbff',
                'backgroundColor' => '#fff',
                'boxBackgroundColor' => '#fff',
                'inputTextColor' => '#525252',
                'inputBorderColor' => '#fff'
            ]);
            $style = array_merge($colors, [
                'fontSize' => Settings::get('fontSize', '16') . 'px',
            ]);
        }

        $config = array_merge([
            'creator' => false,
            'lang' => Constants::getLang(),
            'userId' => get_current_user_id(),
            'networks' => Settings::getNetworks(),
            'baseUrl' => Helpers::getCurrentUrl(),
            'debug' => Settings::get('debugging', false),
            'apiUrl' => Helpers::getAPI('RestAPI')->getUrl(),
            'isAdmin' => self::isAdmin(get_current_user_id()),
            'deployedContracts' => Service::getDeployedContracts(),
            'adapterConfig' => [
                'projectId' => Settings::get('projectId'),
            ],
            'imagesUrl' => Helpers::getProp('pluginUrl') . 'assets/images/icons/',
            'hideProgressBar' => Settings::get('hideProgressBar', false),
            'tonManifestUrl' => Helpers::getProp('pluginUrl') . 'manifest.json',
        ], $config);

        if (boolval(Settings::get('redirection', false))) {
            if ($redirectPage = Settings::get('redirectionPage', false)) {
                $config['redirectAfterPurchase'] = $redirectPage;
            }
        }

        $applyForSingle = Settings::get('applyOnlyForSinglePresaleBox', false);
        $condition = !$applyForSingle ? isset($style) : isset($style) && isset($config['single']);

        if ($condition && isset($style)) {
            $config['style'] = $style;
        }

        $config = Hook::callFilter('config', $config);

        add_filter('script_loader_tag', function ($tag, $handle, $src) {
            if (false !== strpos($handle, 'tokenico-')) {
                // The Vite app bundle (app/main.js) is imported by its own
                // lazily-loaded route chunks through a relative "./main.js"
                // specifier that carries NO query string. If WordPress serves the
                // entry as "app/main.js?ver=X", the browser treats
                // "main.js?ver=X" (this script tag) and "main.js" (the chunk
                // import) as two different modules and evaluates the whole bundle
                // TWICE -> two separate Vue + Pinia instances. Store lookups then
                // hit an uninitialised Pinia copy and throw:
                //   "Cannot read properties of undefined (reading '_s')".
                // Stripping the version query makes the script-tag URL match the
                // import URL so the bundle is evaluated only once.
                if (false !== strpos($src, '/app/main')) {
                    $src = remove_query_arg('ver', $src);
                }
                return '<script type="module" src="' . esc_url($src) . '" id="' . $handle . '"></script>';
            }
            return $tag;
        }, 10, 3);

        // Resolve the content-hashed Vite entry (main-[hash].js), so the
        // <script> URL matches the relative import URL the chunks use (the strip
        // filter above also drops ?ver). Falls back to the fixed name if the
        // build hasn't produced a hashed entry. The pro build emits its own
        // bundle into the tokenico-premium addon, so when pro is loaded we must
        // glob that addon's assets/js/app dir instead of the core plugin's.
        $resolveAppEntry = function (string $baseDir): string {
            $hashedEntries = glob($baseDir . 'assets/js/app/main-*.js');
            if (!empty($hashedEntries)) {
                return 'app/' . basename($hashedEntries[0]);
            }
            return 'app/main.js';
        };

        if (defined('BP_TOKENICO_PRO_LOADED')) {
            $pro = Helpers::getAddon('pro');
            $key1 = $pro->addScript($resolveAppEntry($pro->getPath()), ['jquery']);
        } else {
            $key1 = Helpers::addScript($resolveAppEntry(Helpers::getProp('pluginDir')), ['jquery']);
        }

        $key = Helpers::addScript('main.js', ['jquery', $key1]);

        wp_localize_script($key, 'Tokenico', $config);
    }

    /**
     * @return object
     */
    public static function getDeployedContracts(): object
    {
        $defaultContracts = [
            // ethereum mainnet
            '1' => [
                'v1' => '0xb1bf5ca11a4c4f95ab46b496757e1dbb1397ec0a'
            ],
            // bnb smart chain mainnet
            '56' => [
                'v1' => '0x6e0a0ba0e4e7433e65e6b4a12860baf43b0b8f06'
            ],
            // avalanche mainnet
            '43114' => [
                'v1' => '0x6e0A0BA0e4E7433E65e6b4a12860Baf43b0b8F06'
            ],
            // polygon mainnet
            '137' => [
                'v1' => '0x6e0a0ba0e4e7433e65e6b4a12860baf43b0b8f06'
            ],
            // ethereum sepolia testnet
            '11155111' => [
                'v1' => '0x4268544Bc35C6e5d4c8e42532fC2D887bEFd3442'
            ],
            // bnb smart chain testnet
            '97' => [
                'v1' => '0xbdD9b663D5a59EdB0D3F02dFCc00B5B566409973'
            ],
            // avalanche fuji testnet
            '43113' => [
                'v1' => '0xA3117b4E81134ccc23e41e7a8E27FBB9b288a718'
            ],
            // polygon amoy testnet
            '80002' => [
                'v1' => '0x07d8bbc12a15fcca5e43a426b875df1d934f3d15'
            ],
            'solana' => [
                'v1' => 'EfS6kbddUFvRUCmH1BwS2HDRQzt3HdFsbhaCxtGqQfog'
            ],
            'solana-devnet' => [
                'v1' => 'ELY7poJmnMiJW3DM634KuabWyBoodm8ipojFWpaGazxJ'
            ],
            'tron-testnet' => [
                'v1' => 'TKWkqji4NGhGUjC7ZAqn53vNpc6LfWmyFX'
            ],
            'ton' => [
                'v1' => 'EQB1Mdu5W0RO_c_2JUP47_VQHZJT5GE_tA14rygcaOrhwj0L'
            ],
            'ton-testnet' => [
                'v1' => 'EQB1Mdu5W0RO_c_2JUP47_VQHZJT5GE_tA14rygcaOrhwj0L'
            ],
        ];


        $deployedContracts = get_option('tokenico_deployed_contracts', '{}');
        $deployedContracts = is_string($deployedContracts)
            ? json_decode($deployedContracts, true)
            : json_decode(json_encode($deployedContracts), true);

        $contracts = self::customMerge($deployedContracts, $defaultContracts);

        return self::convertToObject(Hook::callFilter('deployed_contracts', $contracts));
    }

    /**
     * @param array<mixed> $array1
     * @param array<mixed> $array2
     * @return array<mixed>
     */
    public static function customMerge(array $array1, array $array2): array
    {
        foreach ($array2 as $key => $value) {
            if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
                $array1[$key] = self::customMerge($array1[$key], $array2[$key]);
            } elseif (!array_key_exists($key, $array1) || $array1[$key] !== $value) {
                $array1[$key] = $value;
            }
        }
        return $array1;
    }

    /**
     * @param array<mixed> $array
     * @return object
     */
    private static function convertToObject(array $array): object
    {
        return json_decode(json_encode($array));
    }

    /**
     * @param array<mixed>|null $presales
     * @return array<mixed>
     */
    public static function presaleBulkFormat(?array $presales): array
    {
        if (!is_array($presales)) {
            return [];
        }

        return array_map(function ($presale) {
            return self::presaleFormat($presale);
        }, $presales);
    }

    /**
     * @param object $presale
     * @return object
     */
    public static function presaleFormat(object $presale): object
    {
        $args = json_decode($presale->args);
        if (is_array($args) && !count($args)) {
            $args = new \stdClass();
        }

        if (is_numeric($presale->networkId)) {
            $presale->networkId = (int) $presale->networkId;
        }

        return (object) [
            'args' => $args,
            'key' => $presale->key,
            'id' => (int) $presale->id,
            'userId' => (int) $presale->userId,
            'networkId' => $presale->networkId,
            'data' => json_decode($presale->data),
            'token' => json_decode($presale->token),
            'networkCode' => $presale->networkCode,
            'adminAddress' => $presale->adminAddress,
            'totalSales' => (float) $presale->totalSales,
            'nativeCurrency' => $presale->nativeCurrency,
            'contractAddress' => $presale->contractAddress,
            'contractVersion' => $presale->contractVersion,
            'metadata' => json_decode($presale->metadata ?? '{}'),
        ];
    }

    /**
     * @param array<mixed>|null $sales
     * @return array<mixed>
     */
    public static function saleBulkFormat(?array $sales): array
    {
        if (!is_array($sales)) {
            return [];
        }

        return array_map(function ($sale) {
            return self::saleFormat($sale);
        }, $sales);
    }

    /**
     * @param object $sale
     * @return object
     */
    public static function saleFormat(object $sale): object
    {
        return (object) [
            'id' => (int) $sale->id,
            'userId' => (int) $sale->userId,
            'presaleKey' => $sale->presaleKey,
            'networkId' => $sale->networkId,
            'token' => json_decode($sale->token),
            'nativeCurrency' => $sale->nativeCurrency,
            'transactionHash' => $sale->transactionHash,
            'transactionUrl' => $sale->transactionUrl,
            'receiverAddress' => $sale->receiverAddress,
            'receiverAddressUrl' => $sale->receiverAddressUrl,
            'tokenAmount' => (float) $sale->tokenAmount,
            'purchaseAmount' => (float) $sale->purchaseAmount,
            'createdAt' => $sale->createdAt * 1000,
        ];
    }
}
