<?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) {
            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->getTestResultObject();
                    },
                    $cest
                ));
                $errors = $testResultObject->errors();

                if (!empty($errors)) {
                    foreach ($errors as $error) {
                        if ($error->failedTest()->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) {
                $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost);
                $remoteWebDriver->quit();
            }
        }
    }

    /**
     * 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;
    }
}
