<?php

namespace Group;

use Facebook\WebDriver\Remote\RemoteWebDriver;
use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler;
use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore;
use Magento\FunctionalTestingFramework\Module\MagentoWebDriver;
use Magento\FunctionalTestingFramework\Module\MagentoAssert;
use Magento\FunctionalTestingFramework\Module\MagentoActionProxies;
use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException;
use Codeception\Lib\ModuleContainer;
use Codeception\Module;

/**
 * Group class is Codeception Extension which is allowed to handle to all internal events.
 * This class itself can be used to listen events for test execution of one particular group.
 * It may be especially useful to create fixtures data, prepare server, etc.
 *
 * INSTALLATION:
 *
 * To use this group extension, include it to "extensions" option of global Codeception config.
 */
class functionalSuiteHooks extends \Codeception\GroupObject
{
    public static $group = 'functionalSuiteHooks';
    private $testCount = 1;
    private $preconditionFailure = null;
    private $currentTestRun = 0;
    private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of functionalSuiteHooks suite %s block ********/\n";
    private static $HOOK_EXECUTION_END = "\n/******** Execution of functionalSuiteHooks suite %s block complete ********/\n";
    /** @var MagentoWebDriver */
    private $webDriver;
    /** @var ModuleContainer */
    private $moduleContainer;

    public function _before(\Codeception\Event\TestEvent $e)
    {
        $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver');
        $this->moduleContainer = $this->webDriver->getModuleContainer();
        // increment test count per execution
        $this->currentTestRun++;
        $this->executePreConditions();

        if ($this->preconditionFailure != null) {
            //if our preconditions fail, we need to mark all the tests as incomplete.
            $e->getTest()->getMetadata()->setIncomplete("SUITE PRECONDITION FAILED:" . PHP_EOL . $this->preconditionFailure);
        }
    }

    private function executePreConditions()
    {
        if ($this->currentTestRun == 1) {
            $this->testCount = $this->getTestCount();

            print sprintf(self::$HOOK_EXECUTION_INIT, "before");

            try {
                if ($this->webDriver->webDriver != null) {
                    $this->webDriver->_restart();
                } else {
                    $this->webDriver->_initializeSession();
                }
                $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before
                $createOneFields['someKey'] = "dataHere";
                PersistedObjectHandler::getInstance()->createEntity(
                    "createOne",
                    "suite",
                    "createEntityOne",
                    [],
                    $createOneFields
                );
                PersistedObjectHandler::getInstance()->createEntity(
                    "createTwo",
                    "suite",
                    "createEntityTwo",
                    ["createEntityOne"]
                );
                PersistedObjectHandler::getInstance()->createEntity(
                    "createThree",
                    "suite",
                    "createEntityThree",
                    []
                );
                PersistedObjectHandler::getInstance()->createEntity(
                    "createFour",
                    "suite",
                    "createEntityFour",
                    ["createEntityTwo", "createEntityThree"]
                );
                $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('createTwo', 'data', 'suite')); // stepKey: clickWithData
                print("Entering Action Group [AC] actionGroupWithTwoArguments");
                $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC
                print("Exiting Action Group [AC] actionGroupWithTwoArguments");
            } catch (\Exception $exception) {
                $this->preconditionFailure = $exception->getMessage();
            }

            // reset configuration and close session
            $this->webDriver->_resetConfig();
            $this->webDriver->webDriver->close();
            $this->webDriver->webDriver = null;

            print sprintf(self::$HOOK_EXECUTION_END, "before");
        }
    }

    public function _after(\Codeception\Event\TestEvent $e)
    {
        $this->executePostConditions($e);
    }

    private function executePostConditions(\Codeception\Event\TestEvent $e)
    {
        if ($this->currentTestRun == $this->testCount) {
            print sprintf(self::$HOOK_EXECUTION_INIT, "after");

            try {
                // Find out if Test in Suite failed, will cause potential failures in suite after
                $cest = $e->getTest();

                //Access private TestResultObject to find stack and if there are any errors (as opposed to failures)
                $testResultObject = call_user_func(\Closure::bind(
                    function () use ($cest) {
                        return $cest->getResultAggregator();
                    },
                    $cest
                ));
                $errors = $testResultObject->errors();

                if (!empty($errors)) {
                    foreach ($errors as $error) {
                        if ($error->getTest()->getTestMethod() == $cest->getName()) {
                            // Do not attempt to run _after if failure was in the _after block
                            // Try to run _after but catch exceptions to prevent them from overwriting original failure.
                            print("LAST TEST IN SUITE FAILED, TEST AFTER MAY NOT BE SUCCESSFUL\n");
                        }
                    }
                }
                if ($this->webDriver->webDriver != null) {
                    $this->webDriver->_restart();
                } else {
                    $this->webDriver->_initializeSession();
                }
                $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: after
                $this->getModuleForAction("deleteEntityByUrl")->deleteEntityByUrl("deleteThis"); // stepKey: delete
                print("Entering Action Group [AC] actionGroupWithTwoArguments");
                $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC
                print("Exiting Action Group [AC] actionGroupWithTwoArguments");
            } catch (\Exception $exception) {
                print $exception->getMessage();
            }

            PersistedObjectHandler::getInstance()->clearSuiteObjects();

            $this->closeSession($this->webDriver);

            print sprintf(self::$HOOK_EXECUTION_END, "after");
        }
    }

    /**
     * Close session method closes current session.
     * If config 'close_all_sessions' is set to 'true' all sessions will be closed.
     *
     * return void
     */
    private function closeSession(): void
    {
        $webDriverConfig = $this->webDriver->_getConfig();
        $this->webDriver->_closeSession();
        if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") {
            $wdHost = sprintf(
                '%s://%s:%s%s',
                $webDriverConfig['protocol'],
                $webDriverConfig['host'],
                $webDriverConfig['port'],
                $webDriverConfig['path']
            );
            $availableSessions = RemoteWebDriver::getAllSessions($wdHost);
            foreach ($availableSessions as $session) {
                try {
                    $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost);
                    $remoteWebDriver->quit();
                } catch (\Exception $exception) {
                    print("Failed trying to quit WebDriver session. Exception message: " . $exception->getMessage() . " Test execution will continue." . PHP_EOL);
                    // Session already closed so nothing to do
                }
            }
        }
    }

    /**
     * Return the module for an action.
     *
     * @param string $action
     * @return Module
     * @throws \Exception
     */
    private function getModuleForAction($action)
    {
        $module = $this->moduleContainer->moduleForAction($action);
        if ($module === null) {
            throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL);
        }
        return $module;
    }

    /**
     * Counts how many tests in group.
     *
     * @return integer
     */
    private function getTestCount()
    {
        $config = $this->getGlobalConfig();
        if (empty($config['groups']) || empty($config['groups'][self::$group])) {
            return $this->testCount;
        }
        $pathToGroupDir = TESTS_BP . DIRECTORY_SEPARATOR . array_first($config['groups'][self::$group]);
        $pathToGroupCests = $pathToGroupDir . "*Cest.php";

        $files = glob($pathToGroupCests);
        if (is_array($files)) {
            $qty = count($files);
            print('In a group "' . self::$group . '" suite executor found ' . $qty . ' tests.' . PHP_EOL);
            return $qty;
        }

        return $this->testCount;
    }
}
