<?php
/**
 * @author 1902 Software
 * @copyright Copyright © 2023 1902 Software (https://1902software.com/magento/)
 * @package WriteTextAI_WriteTextAI
 */

namespace WriteTextAI\WriteTextAI\Setup\Patch\Data;

use Magento\Authorization\Model\Acl\Role\Group as RoleGroup;
use Magento\Authorization\Model\ResourceModel\Role\CollectionFactory as RoleCollectionFactory;
use Magento\Authorization\Model\RoleFactory;
use Magento\Authorization\Model\RulesFactory;
use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class ApplyWriteTextAiDataPatch implements DataPatchInterface
{
    public const CATEGORY_GRID_LISTING = 'wtai_categories_grid_listing';

    public const PRODUCT_GRID_LISTING = 'wtai_products_grid_listing';

    public const ADMIN_ROLE = 'WriteText.ai Administrator';

    public const EDITOR_ROLE = 'WriteText.ai Editor';

    public const CONTRIBUTOR_ROLE = 'WriteText.ai Contributor';

    public const VIEWER_ROLE = 'WriteText.ai Viewer';

    /**
     * @var ModuleDataSetupInterface
     */
    private $moduleDataSetup;

    /**
     * @var RoleFactory
     */
    private $roleFactory;

    /**
     * @var RulesFactory
     */
    private $rulesFactory;

    /**
     * @var RoleCollectionFactory
     */
    private $roleCollectionFactory;

    /**
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param RoleFactory $roleFactory
     * @param RulesFactory $rulesFactory
     * @param RoleCollectionFactory $roleCollectionFactory
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        RoleFactory $roleFactory,
        RulesFactory $rulesFactory,
        RoleCollectionFactory $roleCollectionFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->roleFactory = $roleFactory;
        $this->rulesFactory = $rulesFactory;
        $this->roleCollectionFactory = $roleCollectionFactory;
    }

    /**
     * Apply data patch
     *
     * @return void
     */
    public function apply()
    {
        $this->resetListingFilters();
        $this->forceExpireUserWebTokens();
        $this->refetchApiSettings();
        $this->manageUserRoles();
    }

    /**
     * Reset listing filters
     *
     * @return void
     */
    private function resetListingFilters()
    {
        $connection = $this->moduleDataSetup->getConnection();

        $connection->delete(
            $this->moduleDataSetup->getTable('ui_bookmark'),
            ['namespace IN (?)' => [self::CATEGORY_GRID_LISTING, self::PRODUCT_GRID_LISTING]]
        );
    }

    /**
     * Force expire user web tokens
     *
     * @return void
     */
    private function forceExpireUserWebTokens()
    {
        $this->moduleDataSetup->getConnection()->delete(
            $this->moduleDataSetup->getTable('writetextai_writetextai_user_web_token')
        );
    }

    /**
     * Refetch API settings
     *
     * @return void
     */
    private function refetchApiSettings()
    {
        $this->moduleDataSetup->getConnection()->delete(
            $this->moduleDataSetup->getTable('writetextai_writetextai_settings')
        );
    }

    /**
     * Manage user roles
     *
     * @return void
     */
    private function manageUserRoles()
    {
        $this->createWriteTextAdmin();
        $this->createWriteTextEditor();
        $this->createWriteTextContributor();
        $this->createWriteTextViewer();
        $this->ensureCorrectRoleRules();
        $this->removeDuplicateRoles();
    }

    /**
     * Check if role exists
     *
     * @param string $roleName
     * @return bool
     */
    private function roleExists(string $roleName): bool
    {
        $roleCollection = $this->roleCollectionFactory->create();
        $roleCollection->addFieldToFilter('role_name', $roleName);
        $size = $roleCollection->getSize();
        $result = $size > 0;
        return $result;
    }

    /**
     * Create WriteText admin role
     *
     * @return void
     */
    private function createWriteTextAdmin()
    {
        if ($this->roleExists(self::ADMIN_ROLE)) {
            return;
        }

        $role = $this->roleFactory->create();
        $role->setName(self::ADMIN_ROLE)
            ->setPid(0)
            ->setRoleType(RoleGroup::ROLE_TYPE)
            ->setUserType(UserContextInterface::USER_TYPE_ADMIN);
        $role->save();

        $rolePermissions = $this->getRolePermissions();
        $resource = $rolePermissions[self::ADMIN_ROLE];
    
        $this->rulesFactory->create()
            ->setRoleId($role->getId())
            ->setResources($resource)
            ->saveRel();
    }

    /**
     * Create WriteText editor role
     *
     * @return void
     */
    private function createWriteTextEditor()
    {
        if ($this->roleExists(self::EDITOR_ROLE)) {
            return;
        }

        $role = $this->roleFactory->create();
        $role->setName(self::EDITOR_ROLE)
            ->setPid(0)
            ->setRoleType(RoleGroup::ROLE_TYPE)
            ->setUserType(UserContextInterface::USER_TYPE_ADMIN);
        $role->save();

        $rolePermissions = $this->getRolePermissions();
        $resource = $rolePermissions[self::EDITOR_ROLE];

        $this->rulesFactory->create()
            ->setRoleId($role->getId())
            ->setResources($resource)
            ->saveRel();
    }

    /**
     * Create WriteText contributor role
     *
     * @return void
     */
    private function createWriteTextContributor()
    {
        if ($this->roleExists(self::CONTRIBUTOR_ROLE)) {
            return;
        }

        $role = $this->roleFactory->create();
        $role->setName(self::CONTRIBUTOR_ROLE)
            ->setPid(0)
            ->setRoleType(RoleGroup::ROLE_TYPE)
            ->setUserType(UserContextInterface::USER_TYPE_ADMIN);
        $role->save();

        $rolePermissions = $this->getRolePermissions();
        $resource = $rolePermissions[self::CONTRIBUTOR_ROLE];

        $this->rulesFactory->create()
            ->setRoleId($role->getId())
            ->setResources($resource)
            ->saveRel();
    }

    /**
     * Create WriteText viewer role
     *
     * @return void
     */
    private function createWriteTextViewer()
    {
        if ($this->roleExists(self::VIEWER_ROLE)) {
            return;
        }

        $role = $this->roleFactory->create();
        $role->setName(self::VIEWER_ROLE)
            ->setPid(0)
            ->setRoleType(RoleGroup::ROLE_TYPE)
            ->setUserType(UserContextInterface::USER_TYPE_ADMIN);
        $role->save();

        $rolePermissions = $this->getRolePermissions();
        $resource = $rolePermissions[self::VIEWER_ROLE];

        $this->rulesFactory->create()
            ->setRoleId($role->getId())
            ->setResources($resource)
            ->saveRel();
    }

    /**
     * Ensure correct role rules
     *
     * @return void
     */
    private function ensureCorrectRoleRules()
    {
        $rolePermissions = $this->getRolePermissions();

        foreach ($rolePermissions as $roleName => $expectedResources) {
            $roleCollection = $this->roleCollectionFactory->create();
            $roleCollection->addFieldToFilter('role_name', $roleName);

            $role = $roleCollection->getFirstItem();
            if ($role && $role->getId()) {
                if (!$this->doRulesMatch($role->getId(), $expectedResources)) {
                    $connection = $this->moduleDataSetup->getConnection();
                    $connection->delete(
                        $this->moduleDataSetup->getTable('authorization_rule'),
                        ['role_id = ?' => $role->getId()]
                    );

                    $this->rulesFactory->create()
                        ->setRoleId($role->getId())
                        ->setResources($expectedResources)
                        ->saveRel();
                }
            }
        }
    }

    /**
     * Check if rules match expected resources
     *
     * @param int $roleId
     * @param array $expectedResources
     * @return bool
     */
    private function doRulesMatch($roleId, $expectedResources)
    {
        $connection = $this->moduleDataSetup->getConnection();
        $select = $connection->select()
            ->from($this->moduleDataSetup->getTable('authorization_rule'), ['resource_id'])
            ->where('role_id = ?', $roleId);

        $currentResources = $connection->fetchCol($select);

        sort($currentResources);
        sort($expectedResources);

        return $currentResources === $expectedResources;
    }

    /**
     * Get role permissions
     *
     * @return array
     */
    private function getRolePermissions()
    {
        $permissions = [
            self::ADMIN_ROLE => [
                'WriteTextAI_WriteTextAI::menu',
                'WriteTextAI_WriteTextAI::transfer',
                'WriteTextAI_WriteTextAI::review',
                'WriteTextAI_WriteTextAI::edit',
                'WriteTextAI_WriteTextAI::product_details',
                'WriteTextAI_WriteTextAI::modify_defaults',
                'WriteTextAI_WriteTextAI::modify_attributes',
                'WriteTextAI_WriteTextAI::modify_tone_style',
                'WriteTextAI_WriteTextAI::modify_words',
                'WriteTextAI_WriteTextAI::generate',
                'WriteTextAI_WriteTextAI::keyword_analysis'
            ],
            self::EDITOR_ROLE => [
                'WriteTextAI_WriteTextAI::menu',
                'WriteTextAI_WriteTextAI::transfer',
                'WriteTextAI_WriteTextAI::review',
                'WriteTextAI_WriteTextAI::edit',
                'WriteTextAI_WriteTextAI::product_details',
                'WriteTextAI_WriteTextAI::modify_attributes',
                'WriteTextAI_WriteTextAI::modify_tone_style',
                'WriteTextAI_WriteTextAI::modify_words',
                'WriteTextAI_WriteTextAI::generate',
                'WriteTextAI_WriteTextAI::keyword_analysis'
            ],
            self::CONTRIBUTOR_ROLE => [
                'WriteTextAI_WriteTextAI::menu',
                'WriteTextAI_WriteTextAI::edit',
                'WriteTextAI_WriteTextAI::product_details',
                'WriteTextAI_WriteTextAI::modify_attributes',
                'WriteTextAI_WriteTextAI::modify_tone_style',
                'WriteTextAI_WriteTextAI::modify_words',
                'WriteTextAI_WriteTextAI::generate',
                'WriteTextAI_WriteTextAI::keyword_analysis'
            ],
            self::VIEWER_ROLE => [
                'WriteTextAI_WriteTextAI::menu'
            ]
        ];

        return $permissions;
    }

    /**
     * Remove duplicate roles
     *
     * @return void
     */
    private function removeDuplicateRoles()
    {
        $roleNames = [
            self::ADMIN_ROLE,
            self::EDITOR_ROLE,
            self::CONTRIBUTOR_ROLE,
            self::VIEWER_ROLE
        ];

        foreach ($roleNames as $roleName) {
            $roleCollection = $this->roleCollectionFactory->create();
            $roleCollection->addFieldToFilter('role_name', $roleName);
            $roleCollection->setOrder('role_id', 'DESC');

            $roles = $roleCollection->getItems();

            if (count($roles) > 1) {
                $keepRole = array_shift($roles);

                foreach ($roles as $duplicateRole) {
                    try {
                        $connection = $this->moduleDataSetup->getConnection();
                        $connection->delete(
                            $this->moduleDataSetup->getTable('authorization_rule'),
                            ['role_id = ?' => $duplicateRole->getId()]
                        );

                        $duplicateRole->delete();
                    } catch (\Exception $e) {
                        continue;
                    }
                }
            }
        }
    }

    /**
     * Get dependencies
     *
     * @return array
     */
    public static function getDependencies()
    {
        return [];
    }

    /**
     * Get aliases
     *
     * @return array
     */
    public function getAliases()
    {
        return [];
    }
}
