<?php
/**
 * @package FishPig_WordPress
 * @author  Ben Tideswell (ben@fishpig.com)
 * @url     https://fishpig.co.uk/magento/wordpress-integration/
 */
declare(strict_types=1);

namespace FishPig\WordPress\App\Theme\Deployment;

use FishPig\WordPress\App\Exception;
use FishPig\WordPress\App\Theme;
use FishPig\WordPress\App\Theme\RemoteHashProvider;
use FishPig\WordPress\App\Theme\DeploymentInterface;

class HttpUpload implements \FishPig\WordPress\App\Theme\DeploymentInterface
{
    /**
     *
     */
    const USERNAME_PREFIX = '_fishpig_auto_';

    /**
     *
     */
    private $appMode = null;

    /**
     *
     */
    private $theme = null;

    /**
     *
     */
    private $themeBuilder = null;

    /**
     *
     */
    private $wpUrl = null;

    /**
     *
     */
    private $requestManager = null;

    /**
     *
     */
    private $resourceConnection = null;

    /**
     *
     */
    private $httpConfig = null;

    /**
     *
     */
    public function __construct(
        \FishPig\WordPress\App\Integration\Mode $appMode,
        \FishPig\WordPress\App\Theme $theme,
        \FishPig\WordPress\App\Theme\Builder $themeBuilder,
        \FishPig\WordPress\App\Url $wpUrl,
        \FishPig\WordPress\App\HTTP\RequestManager $requestManager,
        \FishPig\WordPress\App\ResourceConnection $resourceConnection,
        \FishPig\WordPress\App\HTTP\Config $httpConfig
    ) {
        $this->appMode = $appMode;
        $this->theme = $theme;
        $this->themeBuilder = $themeBuilder;
        $this->wpUrl = $wpUrl;
        $this->requestManager = $requestManager;
        $this->resourceConnection = $resourceConnection;
        $this->httpConfig = $httpConfig;
    }

    /**
     *
     */
    public function isEnabled(): bool
    {
        return false;
    }

    /**
     *
     */
    public function deploy(): void
    {
        try {
            $user = $this->createWordPressUser();

            $this->deployUsingHttpUpload(
                $this->createLocalZipFile(),
                $user['username'],
                $user['password']
            );
        } finally {
            $this->cleanUpAutoGeneratedUsers();
        }
    }

    /**
     *
     */
    private function createLocalZipFile(): string
    {
        $localHash = $this->theme->getLocalHash();
        $blob = $this->themeBuilder->getBlob();
        $zipFile = $this->getTempFile();

        file_put_contents($zipFile, $blob);

        if (!is_file($zipFile)) {
            throw new Exception(
                sprintf(
                    'Cannot create ZIP file at "%s"',
                    $zipFile
                )
            );
        }

        return $zipFile;
    }

    /**
     *
     */
    private function deployUsingHttpUpload(
        string $zipFile,
        string $username,
        string $password
    ): void {
        try {
            $cookieFile = $this->getTempFile();
            $userAgent = \FishPig\WordPress\App\HTTP\Client\Curl::USERAGENT;

            $ch = curl_init();

            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_HEADER => true,
                CURLOPT_URL => $this->wpUrl->getSiteUrl('wp-login.php'),
                CURLOPT_USERAGENT => $userAgent . ' ThemeUpdater',
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_TIMEOUT => 5,
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => [
                    'log' => $username,
                    'pwd' => $password,
                ],
                CURLOPT_COOKIESESSION => true,
                CURLOPT_COOKIEJAR => $cookieFile,
                CURLOPT_COOKIEFILE => $cookieFile
            ]);

            if ($resolveHeaderValue = $this->httpConfig->getCurlOptionResolve()) {
                curl_setopt_array($ch, [
                    CURLOPT_DNS_USE_GLOBAL_CACHE => false,
                    CURLOPT_RESOLVE => $resolveHeaderValue
                ]);
            }

            curl_setopt($ch, CURLOPT_URL, 'https://wp.local.fp.com/wp/wp-login.php');

            $loginResult = $this->curlExec($ch);

            if (strpos($loginResult, '/wp/wp-admin/admin-ajax.php') === false) {
                throw new Exception('Login to the WordPress Admin failed.');
            }

            // Get the theme upload page. We need this to get the nonce
            curl_setopt(
                $ch,
                CURLOPT_URL,
                $this->wpUrl->getSiteUrl('wp-admin/theme-install.php?browse=popular')
            );

            $themeListResult = $this->curlExec($ch);

            if (!($formHtml = $this->pregMatch('/<form[^>]+wp-upload-form[^>]+>.+<\/form>/Usi', $themeListResult, 0))) {
                throw new Exception(
                    'Cannot find theme upload form. Unable to get NONCE.'
                );
            }

            if (!($formAction = $this->pregMatch('/action="(.*)"/U', $formHtml, 1))) {
                throw new Exception('Cannot find theme upload form action');
            }

            $inputs = $this->getFormInputs($formHtml);

            if (empty($inputs['_wpnonce'])) {
                throw new Exception('Unable to get _wpnonce for theme upload.');
            }

            curl_setopt_array($ch, [
                CURLOPT_URL => $formAction,
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => array(
                    '_wpnonce' => $inputs['_wpnonce'],
                    'themezip' => new \CurlFile(
                        $zipFile,
                        'application/zip',
                        basename($zipFile)
                    )
                )
            ]);

            $themeUploadResult = $this->curlExec($ch);

            // Theme has been uploaded but it may be asking us to confirm.
            // This will only happen if we are updating rather than installing new
            $updateUrlRelative = str_replace(
                '&amp;',
                '&',
                $this->pregMatch('/href="([^"]+action=upload-theme&[^"]+)"/', $themeUploadResult, 1) ?? ''
            );

            if ($updateUrlRelative) {
                $updateUrl = $this->wpUrl->getSiteUrl(
                    'wp-admin/' . $updateUrlRelative
                );

                curl_setopt($ch, CURLOPT_URL, $updateUrl);
                $this->curlExec($ch);
            }

            // Ensure that the theme is enabled in the WP Admin
            $this->theme->enable();

            curl_setopt($ch, CURLOPT_URL, $this->wpUrl->getSiteUrl('index.php?_fishpig=theme.update'));

            $this->curlExec($ch);

            if ($this->theme->getLocalHash()  !== $this->theme->getRemoteHash()) {
                throw new Exception(
                    sprintf(
                        'Remote hash "%s" does not match local hash "%s"',
                        $this->theme->getRemoteHash(),
                        $this->theme->getLocalHash()
                    )
                );
            }
        } finally {
            if (!empty($ch)) {
                curl_close($ch);
            }
        }
    }

    /**
     *
     */
    private function getFormInputs(string $formHtml): array
    {
        if (!($inputHtmls = $this->pregMatchAll('/<input[^>]+>/Usi', $formHtml, 0))) {
            return [];
        }

        $inputs = [];
        foreach ($inputHtmls as $inputHtml) {
            if ($argMatches = $this->pregMatchAll('/ (name|value)=([\'"]{1})([^\2]+)\2/U', $inputHtml)) {
                $nameIndex = array_search('name', $argMatches[1]);
                $valueIndex = array_search('value', $argMatches[1]);
                if ($nameIndex !== false && $valueIndex !== false) {
                    $inputs[$argMatches[3][$nameIndex]] = $argMatches[3][$valueIndex];
                }
            }
        }

        return $inputs;
    }

    /**
     *
     */
    private function pregMatch($pattern, $value, $index = null)
    {
        if (preg_match($pattern, $value, $matches)) {
            if ($index === null) {
                return $matches;
            }

            if (isset($matches[$index])) {
                return $matches[$index];
            }
        }

        return null;
    }

    /**
     *
     */
    private function pregMatchAll($pattern, $value, $index = null)
    {
        if (preg_match_all($pattern, $value, $matches)) {
            if ($index === null) {
                return $matches;
            }

            if (isset($matches[$index])) {
                return $matches[$index];
            }
        }

        return null;
    }

    /**
     *
     */
    private function getTempFile(): string
    {
        $tmpFileStream = tmpfile();
        return stream_get_meta_data($tmpFileStream)['uri'];
    }

    /**
     *
     */
    private function createWordPressUser(): array
    {
        $username = self::USERNAME_PREFIX . md5('t1m3' . time());
        $password = md5('4ut0::' . $username);
        $db = $this->resourceConnection->getConnection();
        $tablePrefix = $this->resourceConnection->getTablePrefix();

        $db->insert(
            $this->getUsersTable(),
            [
                'user_login' => $username,
                'user_pass' => md5($password)
            ]
        );

        $userId = $db->lastInsertId();

        $metaValues = [
            $tablePrefix . 'capabilities' => 'a:1:{s:13:"administrator";s:1:"1";}',
            $tablePrefix . 'user_level' => 10
        ];

        foreach ($metaValues as $metaKey => $metaValue) {
            $db->insert(
                $this->getUsersMetaTable(),
                [
                    'user_id' => $userId,
                    'meta_key' => $metaKey,
                    'meta_value' => $metaValue
                ]
            );
        }

        return [
            'id' => $userId,
            'username' => $username,
            'password' => $password
        ];
    }

    /**
     *
     */
    private function cleanUpAutoGeneratedUsers(): void
    {
        $db = $this->resourceConnection->getConnection();

        $userIds = $db->fetchCol(
            $db->select()->from(
                $this->getUsersTable(),
                'ID'
            )->where(
                'user_login LIKE ?',
                self::USERNAME_PREFIX . '%'
            )
        );

        if ($userIds) {
            $db->delete(
                $this->getUsersTable(),
                $db->quoteInto('ID IN (?)', $userIds)
            );

            $db->delete(
                $this->getUsersMetaTable(),
                $db->quoteInto('user_id IN (?)', $userIds)
            );
        }
    }

    /**
     *
     */
    private function getUsersTable(): string
    {
        return $this->resourceConnection->getTable('users');
    }

    /**
     *
     */
    private function getUsersMetaTable(): string
    {
        return $this->resourceConnection->getTable('usermeta');
    }

    /**
     *
     */
    private function curlExec($ch): string
    {
        $result = curl_exec($ch);

        $error = curl_error($ch);
        $errorNo = curl_errno($ch);

        if ($error || $errorNo) {
            throw new \Exception(
                sprintf(
                    'CURL %d: %s',
                    $errorNo ?? 0,
                    $error ?? ''
                ),
                (int)$errorNo
            );
        }

        return $result ?: '';
    }
}
