<?php
// Copyright 1999-2017. Plesk International GmbH. All rights reserved.

namespace Plesk\MigrationLibV8\MailImport;
use Plesk\MigrationLibV8\Common\SessionDirectory;
use Plesk\MigrationLibV8\MailImport\Backend\Files\MigrationList;
use Plesk\MigrationLibV8\MailImport\Backend\Tool;
use Plesk\MigrationLibV8\MailImport\Entity\Status\Import;
use Plesk\MigrationLibV8\MailImport\Entity\Status\Issue;
use Plesk\MigrationLibV8\MailImport\Entity\Status\Task;
use Plesk\MigrationLibV8\Utils\ArrayUtils;

/**
 * A wrapper around backend of Plesk Migrator to perform mail import using IMAP protocol.
 */
class Manager
{
    /**
     * Prepare for mail import - configure session for specified site.
     * Once session is configured, you should call "scheduleImport" to schedule import of specific mail addresses.
     *
     * This function is executed quickly: it just creates/modifies configuration files.
     *
     * To perform mail import, the following workflow is suggested:
     *
     * 1. Obtain a domain object for the target site, to which you want to import mail messages.
     * For example, ask customer to select domain from a list of available domains,
     * or use domain from the current context. The domain must be either subscription's main domain
     * or addon domain. Mail import to subdomains is not supported as they can not have mailboxes.
     *
     * $domain = new \pm_Domain(123);
     *
     * 2. Obtain a session object. A session is a directory which contains configuration files,
     * logs, reports and other temporary data for Plesk Migrator's backend. Each session has an unique string ID.
     * It is recommended to use domain ID as a part of session ID.
     *
     * $session = \Plesk\MigrationLibV8\Common\SessionDirectory::getForCurrentExtension(
     *     'mail-migration-' . $domain->getId()
     * );
     *
     * 3. Create mail import manager:
     *
     * $manager = new \Plesk\MigrationLibV8\MailImport\Manager();
     *
     * 4. Configure session directory:
     *
     * $manager->configureSession($session, $domain);
     *
     * 5. Schedule import of a mailbox:
     *
     * $mailAddressPairs = [
     *     new MailAddressPair(
     *         'john@example.com', '123qwe', 'tom', '456asd'
     *     ),
     *     new MailAddressPair(
     *         'bob@example.com', '123qwe', 'bob', '456asd'
     *     ),
     * ];
     * $manager->scheduleImport($session, $mailAddressPairs);
     *
     * 6. Once you've scheduled import, start task runner which processes schedules import tasks:
     *
     * $manager->startTasksRunner($session);
     *
     * Task runner works in synchronous way on Windows and in asynchronous way on Linux. That is quite weird,
     * the difference is caused by difficulties implementing asynchronous command execution on Windows.
     *
     * To improve stability, it is useful to start task runner periodically, for example once a minute.
     * If another task runner is already running, the new one will exit immediately. In that case
     * if server was rebooted, or task runner has stopped for any reason, import will continue to work.
     *
     * 7. Once task runner is started, poll import status of imported/scheduled mail address pairs:
     *
     * $status = $manager->getImportStatus($session);
     *
     * 8. Additionally, you have ability to cancel running mail import with "cancelImport" method.
     *
     * @param SessionDirectory $session
     * @param \pm_Domain $site
     */
    public function configureSession(SessionDirectory $session, \pm_Domain $site)
    {
        $session->initialize();
        $sessionObjects = SessionObjects::getInstance($session);

        $config = $sessionObjects->getConfig();
        $config->initDefaultOptions();
        $config->setSessionDirectory($session->getId());
        $config->setTargetOS(\pm_ProductInfo::isUnix() ? 'unix' : 'windows');
        $config->setSourceType('mail');
        $config->setSourceTargetDomain($site->getName());
        $config->save();
    }

    /**
     * Schedule import of mail messages.
     *
     * Schedule import works fast (in most cases less than a second).
     * Once import is scheduled, you should call "startTasksRunner" to actually start import.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\MailImport\Entity\ImportOperation\MailAddressPair[] $mailAddressPairs
     * @param bool $isAsyncStatusReport use for different status reporting modes
     * @return string task ID
     */
    public function scheduleImport(
        SessionDirectory $session,
        $mailAddressPairs, $isAsyncStatusReport=false
    ) {
        $sessionObjects = SessionObjects::getInstance($session);

        $progress = $sessionObjects->getProgress();
        $progress->cleanup();

        $migrationList = $this->_createMigrationListWithRandomName($session, $mailAddressPairs);

        $tool = new Tool();
        $taskId = $tool->addMailMigrationTasks(
            $sessionObjects->getConfig(), $migrationList->getFilePath(), $isAsyncStatusReport
        );
        return $taskId;
    }

    /**
     * Get global import status and import issues for mail address pairs.
     *
     * Workflow of import is the following:
     * - schedule import
     * ("scheduleImport" method)
     * - start task runner
     * ("startTasksRunner" method)
     * - periodically (e.g. once a second) poll import status with that function,
     * display status/issues in user interface.
     *
     * @param SessionDirectory $session
     * @return Import
     */
    public function getGlobalStatus(SessionDirectory $session)
    {
        $sessionObjects = SessionObjects::getInstance($session);
        $status = $sessionObjects->getMailMigrationStatus(null);

        $statusData = $status->getStatusData();

        $status = [];

        $mailAddressPairStatusData = ArrayUtils::getElementDefaultArray($statusData, 'mail_address_pairs');

        foreach ($mailAddressPairStatusData as $mailAddressPair) {
            $status[] = new Import\MailAddressPair(
                $mailAddressPair['source_email'], $mailAddressPair['target_mailname'],
                $this->_convertIssuesToObject(
                    ArrayUtils::getElementDefaultArray($mailAddressPair, 'issues')
                ),
                $mailAddressPair['status'] ?? null,
                $mailAddressPair['step'] ?? null,
                $mailAddressPair['error_id'] ?? null,
                $mailAddressPair['error_message'] ?? null,
                $mailAddressPair['restored_message_num'] ?? null
            );
        }

        return new Import($status);
    }

    /**
     * Get task status.
     *
     * @param SessionDirectory $session
     * @param string $taskId
     * @return Task
     */
    public function getTaskStatus(
        SessionDirectory $session, $taskId
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $status = $sessionObjects->getMailMigrationStatus($taskId);
        $statusData = $status->getStatusData();

        return new Task(
            $statusData['status'] ?? null,
            $statusData['step'] ?? null,
            $statusData['error_id'] ?? null,
            $statusData['error_message'] ?? null,
            $statusData['error_details'] ?? null
        );
    }

    /**
     * Cancel scheduled or running import of specified mail addresses.
     *
     * Operation is synchronous - function returns control once import is actually cancelled.
     * Usually that is fast, but sometimes could take up to a minute, for example in case of hanged connection
     * to the source server.
     *
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\MailImport\Entity\ImportOperation\MailAddressPair[] $mailAddressPairs
     */
    public function cancelImport(
        SessionDirectory $session,
        $mailAddressPairs
    ) {
        $sessionObjects = SessionObjects::getInstance($session);

        $progress = $sessionObjects->getProgress();
        $progress->cleanup();

        $migrationList = $this->_createMigrationListWithRandomName($session, $mailAddressPairs);

        $migratorTool = new Tool();
        $migratorTool->cancelMailMigrationTasks($sessionObjects->getConfig(), $migrationList->getFilePath());
    }

    /**
     * Start task runner, which runs import for previously scheduled imports of mail messages.
     *
     * On Unix this function returns once the task runner was started (asynchronously).
     * On Windows this function returns only once the task runner was finished (synchronously).
     * The difference is caused by difficulties implementing asynchronous command execution on Windows.
     *
     * @param SessionDirectory $session
     */
    public function startTasksRunner(
        SessionDirectory $session
    ) {
        $sessionObjects = SessionObjects::getInstance($session);

        $migratorTool = new Tool();
        $migratorTool->runMailMigrationTasks($sessionObjects->getConfig());
    }

    /**
     * @param SessionDirectory $session
     * @param \Plesk\MigrationLibV8\MailImport\Entity\ImportOperation\MailAddressPair[] $mailAddressPairs
     * @return MigrationList
     */
    private function _createMigrationListWithRandomName(
        SessionDirectory $session, $mailAddressPairs
    ) {
        $sessionObjects = SessionObjects::getInstance($session);
        $migrationListSuffix = (new \DateTime('now'))->format('Y-m-d-H-i-s');
        $migrationList = $sessionObjects->getMailMigrationList($migrationListSuffix);
        $migrationList->write($mailAddressPairs);
        return $migrationList;
    }

    private function _convertIssuesToObject($issuesArray)
    {
        $issues = [];
        foreach ($issuesArray as $issue) {
            $issues[] = new Issue(
                $issue['severity'], $issue['execution_date'], $issue['problem_text'], $issue['details_text']
            );
        }
        return $issues;
    }
}
