<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows\Automation;

use Exception;
use idoit\Component\FeatureManager\FeatureManager;
use idoit\Component\Logger;
use idoit\Console\IdoitConsoleApplication;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Argument\CollectionArgumentConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Argument\StringArgumentConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Command\BuilderConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Command\CollectionConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Command\OptionsOrderConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Command\StaticConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Command\WhitelistConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\CategoryDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\DynamicGroupsDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\JDisc\JDiscDetailedLoggingDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\JDisc\JDiscDiscoveryJobDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\JDisc\JDiscGroupDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\JDisc\JDiscModeDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\JDisc\JDiscProfileDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\JDisc\JDiscServerDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\LDAPServerDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\Notification\NotificationIdDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\Notification\NotificationTypeDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\ObjectTypeDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\ProfileDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\DataSource\ReportDataSource;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\IdoitCommandConfiguration;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\BooleanOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\BooleanOptionConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\CollectionOptionConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\DynamicStringMultiSelectOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\DynamicStringSelectOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\JDisc\JDiscDeviceStringOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\JDisc\JDiscDiscoveryJobOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\OverwriteConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\SearchIndex\SearchIndexCategoryOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\SingleObjectOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\SkipConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\StringArrayOptionConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\StringOption;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\StringOptionConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\StringSelectOptionConverter;
use idoit\Module\SyneticsFlows\Automation\Action\ActionType\IdoitCommand\Option\SyncDynamicGroups\SyncDynamicGroupsOption;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\ApiCallFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\CallIdoitCommandFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\CollectionExecutionFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\CreateObjectFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\PlanExecutionFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\ProcessCategoryChanges;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\RankObjectFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\SendEmailFactory;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionFactory\UpdateDataFactory;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\ApplyCategoryChanges;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\CallIdoitCommandExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\CollectionExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\CreateObjectExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\PerformApiCallExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\PlannedExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\RankObjectExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\SendEmailExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\UpdateDataExecution;
use idoit\Module\SyneticsFlows\Automation\Action\PerformExecution\ValidateCategoryChanges;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionBuilder\CmdbConditionBuilder;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionMatcher\AndConditionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionMatcher\ArrayConditionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionMatcher\ObjectBasedConditionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionMatcher\OrConditionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionMatcher\TimeConditionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\ConditionMatcher\UserConditionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\ButtonInvocationMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\CategoryCreatedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\CategoryEntryRankedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\CategoryUpdatedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\CollectionMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\ObjectCreatedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\ObjectPurgedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\ObjectRankedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\ObjectRecycledMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\ObjectUpdatedMatcher;
use idoit\Module\SyneticsFlows\Automation\Trigger\InvocationMatcher\TimeInvocationMatcher;
use idoit\Module\SyneticsFlows\Model\AutomationDao;
use idoit\Module\SyneticsFlows\Model\CategoryDao;
use idoit\Module\SyneticsFlows\Model\CiObjectDao;
use idoit\Module\SyneticsFlows\Model\ExecutionDao;
use idoit\Module\SyneticsFlows\Model\ObjectTypeDao;
use idoit\Module\SyneticsFlows\Model\ScheduleDao;
use idoit\Module\SyneticsFlows\Session\UserSwitcher;
use idoit\Module\SyneticsFlows\Template\TemplateFactory;
use isys_application;
use isys_cmdb_dao_category_s_group_type;
use isys_cmdb_dao_category_s_person_assigned_groups;
use isys_component_database;
use isys_component_session;
use isys_jdisc_dao;
use isys_ldap_dao;
use isys_module_jdisc;
use isys_notifications_dao;
use isys_tenantsettings;
use Monolog\Handler\NullHandler;

class TriggerFacade
{
    public static function getService(): TriggerService
    {
        $database = isys_application::instance()->container->get('database');
        if (!$database instanceof isys_component_database) {
            throw new Exception('Incorrect configuration');
        }

        $session = isys_application::instance()->container->get('session');
        if (!$session instanceof isys_component_session) {
            throw new Exception('There is no session which can be used.');
        }

        $automationDao = new AutomationDao($database);
        $executionDao = self::getExecutionDao();
        $conditionMatcher = new ArrayConditionMatcher();
        $conditionMatcher->add(new AndConditionMatcher($conditionMatcher));
        $conditionMatcher->add(new OrConditionMatcher($conditionMatcher));
        $conditionMatcher->add(new TimeConditionMatcher());
        $conditionMatcher->add(new UserConditionMatcher(new isys_cmdb_dao_category_s_person_assigned_groups($database)));
        $conditionMatcher->add(
            new ObjectBasedConditionMatcher(new CmdbConditionBuilder($database), new CiObjectDao($database))
        );
        $categoryDao = new CategoryDao($database);
        $matcher = new CollectionMatcher([
            new ButtonInvocationMatcher($automationDao),
            new ObjectCreatedMatcher($automationDao),
            new ObjectUpdatedMatcher($automationDao),
            new ObjectRankedMatcher($automationDao),
            new ObjectRecycledMatcher($automationDao),
            new CategoryEntryRankedMatcher($automationDao),
            new CategoryCreatedMatcher($automationDao),
            new TimeInvocationMatcher(new ScheduleDao($database)),
            new CategoryUpdatedMatcher($automationDao),
            new ObjectPurgedMatcher($automationDao),
        ], $conditionMatcher);
        $template = new TemplateFactory();
        $processChanges = new ProcessCategoryChanges();
        $baseFactory = new CollectionExecutionFactory([
            new CreateObjectFactory($template, $processChanges),
            new ApiCallFactory($template),
            new UpdateDataFactory($template, $processChanges),
            new SendEmailFactory($template),
            new RankObjectFactory(),
            new CallIdoitCommandFactory(),
        ]);
        $factory = new CollectionExecutionFactory([
            new PlanExecutionFactory(),
            $baseFactory
        ]);
        $applyChanges = new ApplyCategoryChanges($categoryDao, $database);
        $validateChanges = new ValidateCategoryChanges($categoryDao, $database);
        $cmdbDao = isys_application::instance()->container->get('cmdb_dao');

        $baseRunner = new CollectionExecution([
            new CreateObjectExecution($applyChanges, $validateChanges),
            new PerformApiCallExecution(),
            new SendEmailExecution(),
            new UpdateDataExecution($applyChanges, $validateChanges),
            new RankObjectExecution($cmdbDao),
            new CallIdoitCommandExecution(self::getIdoitCommandConfiguration()),
        ]);
        $runner = new CollectionExecution([
            new PlannedExecution($automationDao, $baseFactory, $baseRunner),
            $baseRunner
        ]);

        $userSwithcer = new UserSwitcher(
            $session,
            $database
        );

        return new TriggerService($matcher, $executionDao, $factory, $runner, $userSwithcer);
    }

    public static function getExecutionDao(): ExecutionDao
    {
        $database = isys_application::instance()->container->get('database');
        if (!$database instanceof isys_component_database) {
            throw new Exception('Incorrect configuration');
        }
        return new ExecutionDao($database);
    }

    public static function getIdoitCommandConfiguration(): IdoitCommandConfiguration
    {
        $database = isys_application::instance()->container->get('database');
        $categoryDao = new CategoryDao($database);
        $categorySource = new CategoryDataSource($categoryDao);
        $language = isys_application::instance()->container->get('language');
        $ldapDao = new isys_ldap_dao($database);
        $groupsTypesDao = new isys_cmdb_dao_category_s_group_type($database);

        $whitelist = [
            'auth-cleanup',
            'contracts-outdated',
            'extend-contracts',
            'ldap-sync',
            'ldap-syncdn',
            'logbook-archive',
            'system-location-fix',
            'search-index',
            'system-categorycleanup',
            'system-objectcleanup',
            'system-objectrelations',
            'sync-dynamic-groups',
        ];

        if (!FeatureManager::isCloud()) {
            $whitelist = array_merge($whitelist, [
                'import-xml',
            ]);
        }

        $overwrite = new OverwriteConverter();

        if (!FeatureManager::isCloud()) {
            $whitelist[] = 'import-csv';
            $overwrite
                ->overwriteOption('import-csv',
                    new DynamicStringSelectOption(
                        'importProfileId',
                        'Profile which should be used to map import file',
                        true,
                        new ProfileDataSource(),
                        null
                    )
                );
        }
        if (!FeatureManager::isCloud() && class_exists('isys_module_jdisc')) {
            $whitelist[] = 'import-jdisc';
            $jdiscDao = isys_jdisc_dao::instance($database);
            $defaultJDiscServer = $jdiscDao->get_jdisc_servers(null, true)->get_row_value('isys_jdisc_db__id');
            $overwrite
                ->overwriteOption('import-jdisc',
                    new DynamicStringSelectOption('server', 'JDisc Server', false, new JDiscServerDataSource($jdiscDao), $defaultJDiscServer)
                )
                ->overwriteOption('import-jdisc',
                    new DynamicStringSelectOption(
                        'profile',
                        'JDisc Profile',
                        true,
                        new JDiscProfileDataSource($jdiscDao),
                        null,
                        ['server']
                    )
                )
                ->overwriteOption('import-jdisc',
                    new DynamicStringSelectOption('group', '', false, new JDiscGroupDataSource($jdiscDao), null, ['server'])
                )
                ->overwriteOption('import-jdisc',
                    new DynamicStringSelectOption(
                        'mode',
                        '**Possible modes are:**
                \
                **"Append"** - The import will only create new objects.
                **"Update"** - The import will try to update already existing objects.
                **"Overwrite"** - The import behaves like the update mode but clears all list categories of the existing object.
                **"Update (newly discovered)"** - The import clears all existing identification keys before the Update mode is triggered.
                **"Overwrite (newly discovered)"** - The import clears all existing identification keys before the Overwrite mode is triggered.
                **"Only create newly scanned devices"** - The import creates only newly scanned jdisc devices, existing ones are skipped.
                **"Update (Existing)"** - Only existing objects will be updated. No new objects are created.',
                        false,
                        new JDiscModeDataSource(),
                        (string) isys_module_jdisc::C__IMPORT_MODE__UPDATE
                    )
                )
                ->overwriteOption(
                    'import-jdisc',
                    new DynamicStringSelectOption(
                        'detailedLogging',
                        '**Log levels:**
                        \
                         **"Low"** - Only notices and warnings are being logged.
                         **"Normal"** - Additionally to the low log level errors are being logged.
                         **"High"** - Additionally to the normal log level debug messages are being logged.',
                        false,
                        new JDiscDetailedLoggingDataSource(),
                        null
                    )
                );

            if (FeatureManager::isFeatureActive('jdisc-import')) {
                $whitelist[] = 'import-jdiscdiscovery';
                $overwrite->overwriteOption('import-jdiscdiscovery',
                    new DynamicStringSelectOption(
                        'server',
                        'JDisc Server',
                        true,
                        new JDiscServerDataSource($jdiscDao),
                        $defaultJDiscServer,
                    )
                );
                $overwrite
                    ->overwriteOption('import-jdiscdiscovery',
                        new JDiscDiscoveryJobOption('discoveryJob', '', true, new JDiscDiscoveryJobDataSource(), 'Discover all', ['server'])
                    )
                    ->overwriteOption('import-jdiscdiscovery',
                        new JDiscDeviceStringOption('deviceHostname', 'Selected device by "hostname"', true, null)
                    )
                    ->overwriteOption('import-jdiscdiscovery',
                        new JDiscDeviceStringOption('deviceHostAddress', 'Selected device by "hostaddress"', true, null)
                    )
                    ->overwriteOption('import-jdiscdiscovery',
                        new JDiscDeviceStringOption('deviceSerialNumber', 'Selected device by "serialnumber"', true, null)
                    );
            }
        }

        if (FeatureManager::isFeatureActive('notification-settings')) {
            $whitelist[] = 'notifications-send';
            $notificationsDao = new isys_notifications_dao($database, new Logger('notifications', [new NullHandler()]));
            $overwrite
                ->overwriteOption('notifications-send',
                    new DynamicStringMultiSelectOption(
                        'notification-ids',
                        'Pass specific notifications',
                        false,
                        new NotificationIdDataSource($notificationsDao),
                        null
                    )
                )
                ->overwriteOption('notifications-send',
                    new DynamicStringMultiSelectOption(
                        'notification-type-ids',
                        'Pass specific notification type IDs to be sent',
                        false,
                        new NotificationTypeDataSource($notificationsDao, $language),
                        null
                    )
                );
        }

        $overwrite
            ->overwriteOption('search-index',
                new SearchIndexCategoryOption('category', 'Prepare category', false, $categorySource, null)
            )
            ->overwriteOption('system-objectrelations',
                new DynamicStringSelectOption(
                    'categoryConstant',
                    '',
                    true,
                    $categorySource,
                    null
                )
            )
            ->overwriteOption('ldap-sync',
                new DynamicStringSelectOption(
                    'ldapServerId',
                    'Configuration Id of the server that should be synced with, else every configured server will be synced',
                    false,
                    new LDAPServerDataSource($ldapDao),
                    null
                )
            )
            ->overwriteOption('ldap-sync',
                new BooleanOption(
                    'connectionRankingActive',
                    'Configuration which reactivates all connections from all reactivated Users.',
                    false,
                    (bool) isys_tenantsettings::get('ldap.connection-ranking-active', true)
                )
            )
            ->overwriteOption('ldap-sync',
                new BooleanOption(
                    'dropExistingRelations',
                    'If an existing ldap group has group member users, outside of these synced users, those will be purged. Yes = drop existing relations, No = ignore existing relations',
                    false,
                    false,
                )
            )
            ->overwriteOption('ldap-sync',
                new BooleanOption(
                    'archiveDeletedGroups',
                    'If a deleted ldap group remains in i-doit, archive or delete it.',
                    false,
                    false,
                )
            )
            ->overwriteOption('ldap-syncdn',
                new DynamicStringSelectOption(
                    'ldapServerId',
                    'Configuration Id of the server that should be synced with, else every configured server will be synced',
                    false,
                    new LDAPServerDataSource($ldapDao),
                    null
                )
            )
            ->overwriteOption('sync-dynamic-groups',
                new SyncDynamicGroupsOption(
                    'groups',
                    'Select groups',
                    false,
                    new DynamicGroupsDataSource($groupsTypesDao),
                    null
                )
            );
        ;

        if (!FeatureManager::isCloud()) {
            $whitelist[] = 'report-export';
            $overwrite
                ->overwriteOption('report-export',
                    new DynamicStringSelectOption('reportId', 'ID of the report', true, new ReportDataSource(), null)
                )
                ->overwriteOption(
                    'report-export',
                    new StringOption(
                        'exportFilename',
                        'File name of export file, without extension (e.g. .pdf). Default is the title of the report',
                        false,
                        null
                    )
                )
                ->overwriteOption(
                    'report-export',
                    new StringOption(
                        'exportPath',
                        'When using a relative path, the base is your i-doit root folder.',
                        true,
                        null
                    )
                );
        }

        if (!FeatureManager::isCloud()) {
            $whitelist[] = 'import-hinventory';
            $objectTypeDao = new ObjectTypeDao($database);
            $clientType = $objectTypeDao->getByType('C__OBJTYPE__CLIENT');
            $overwrite
                ->overwriteOption('import-hinventory',
                    new DynamicStringSelectOption(
                        'objectType',
                        'Import by given object type',
                        true,
                        new ObjectTypeDataSource($objectTypeDao),
                        $clientType?->getId()
                    )
                )
                ->overwriteOption('import-hinventory',
                    new SingleObjectOption('objectId', 'Import only given object', false, null)
                )
            ;
        }

        $optionConverter = new CollectionOptionConverter([
            new SkipConverter(),
            $overwrite,
            new BooleanOptionConverter(),
            new StringSelectOptionConverter(),
            new StringOptionConverter(),
            new StringArrayOptionConverter(),
        ]);

        $argumentConverter = new CollectionArgumentConverter([
            new StringArgumentConverter(),
        ]);
        $optionsOrderConverter = new OptionsOrderConverter([
            'import-jdisc' => [
                'server',
                'profile',
                'group',
            ]
        ]);
        $commandBuilder = new BuilderConverter($optionConverter, $argumentConverter, $optionsOrderConverter);
        $whitelistConverter = new WhitelistConverter($commandBuilder, $whitelist);
        $staticBuilder = new StaticConverter([]);

        $converter = new CollectionConverter([
            $staticBuilder,
            $whitelistConverter
        ]);

        return new IdoitCommandConfiguration(new IdoitConsoleApplication(), $converter);
    }
}
