<?php

use idoit\Module\JDisc\Service\Archiver;
use idoit\Module\JDisc\Helper\JDiscProgressBarInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use idoit\Module\JDisc\Model\JDiscCommandQueue;

class isys_jdisc_dao_import
{
    /**
     * @var SymfonyStyle|null
     */
    private ?SymfonyStyle $output = null;

    /**
     * @var JDiscProgressBarInterface|null
     */
    private ?JDiscProgressBarInterface $progressBar = null;

    /**
     * @var CommandQueue|null
     */
    private ?JDiscCommandQueue $commandQueue = null;

    /**
     * @var isys_component_database
     */
    private $db;

    /**
     * @var isys_module_jdisc
     */
    private $jdiscInstance;

    /**
     * @var array
     */
    private $clearOptions = [];

    /**
     * @var array
     */
    private $statistics = [];

    /**
     * @param isys_component_database $db
     * @param isys_module_jdisc       $jdiscModuleInstance
     * @param array                   $clearOptions
     */
    public function __construct(isys_component_database $db, isys_module_jdisc $jdiscModuleInstance, array $clearOptions = [])
    {
        $this->db = $db;
        $this->jdiscInstance = $jdiscModuleInstance;
        $this->clearOptions = $clearOptions;
    }

    /**
     * @param SymfonyStyle $output
     *
     * @return $this
     */
    public function setOutput(SymfonyStyle $output): self
    {
        $this->output = $output;
        return $this;
    }

    /**
     * @param JDiscProgressBarInterface $progressBar
     *
     * @return self
     */
    public function setProgressBar(JDiscProgressBarInterface $progressBar): self
    {
        $this->progressBar = $progressBar;
        return $this;
    }

    /**
     * @param JDiscCommandQueue $commandQueue
     *
     * @return self
     */
    public function setCommandQueue(JDiscCommandQueue $commandQueue): self
    {
        $this->commandQueue = $commandQueue;
        return $this;
    }

    /**
     * @return array
     * @throws isys_exception_database
     */
    public function getCurrentStats(): array
    {
        $networkInstance = isys_jdisc_dao_network::instance($this->db);
        /**
         * Create statistics
         */
        return [
            'max_obj'              => $networkInstance
                ->retrieve('SELECT COUNT(*) AS cnt FROM isys_obj;')
                ->get_row_value('cnt'),
            'max_cluster_members'  => $networkInstance
                ->retrieve('SELECT COUNT(*) AS cnt FROM isys_catg_cluster_members_list;')
                ->get_row_value('cnt'),
            'max_blades'           => $networkInstance
                ->retrieve('SELECT COUNT(*) AS cnt FROM isys_cats_chassis_list;')
                ->get_row_value('cnt'),
            'max_port_connections' => $networkInstance
                ->retrieve('SELECT COUNT(*) AS cnt FROM isys_cable_connection;')
                ->get_row_value('cnt'),
            'max_ports'            => $networkInstance
                    ->retrieve('SELECT COUNT(*) AS cnt FROM isys_catg_port_list;')
                    ->get_row_value('cnt') + $networkInstance
                    ->retrieve('SELECT COUNT(*) AS cnt FROM isys_catg_log_port_list;')
                    ->get_row_value('cnt'),
            'max_vlan'             => $networkInstance
                    ->retrieve('SELECT COUNT(*) AS cnt FROM isys_cats_layer2_net_assigned_ports_list;')
                    ->get_row_value('cnt') + $networkInstance
                    ->retrieve('SELECT COUNT(*) AS cnt FROM isys_catg_log_port_list_2_isys_obj;')
                    ->get_row_value('cnt'),
            'max_sw'               => $networkInstance
                ->retrieve('SELECT COUNT(*) AS cnt FROM isys_catg_application_list;')
                ->get_row_value('cnt'),
            'time'           => microtime(true)
        ];
    }


    /**
     * @param array             $jdiscToIdoitObjects
     * @param array             $titleMap
     *
     * @return void
     * @throws \idoit\Exception\JsonException
     * @throws isys_exception_cmdb
     * @throws isys_exception_dao
     * @throws isys_exception_database
     * @throws isys_exception_general
     */
    public function executeFinalImportStep($jdiscToIdoitObjects, $titleMap = [], $triggerSearchReIndex = false)
    {
        if ($this->output) {
            $this->output->text('Step 1: Updating cluster assignments.');
        }

        $clusterInstance = isys_jdisc_dao_cluster::instance($this->db);
        $devicesInstance = isys_jdisc_dao_devices::instance($this->db);
        $networkInstance = isys_jdisc_dao_network::instance($this->db);
        $softwareInstance = isys_jdisc_dao_software::instance($this->db);
        $connectionEndpointInstance = isys_jdisc_dao_category_connection_endpoint::instance($this->db);
        $softwareDatabaseInstance = isys_jdisc_dao_software_database::instance($this->db);

        $clusterInstance
            ->assign_clusters($jdiscToIdoitObjects, $networkInstance
                ->get_vrrp_addresses());

        if ($this->output) {
            $this->output->text('Step 2: Updating cluster members.');
        }
        $clusterInstance
            ->update_cluster_members($jdiscToIdoitObjects);

        if ($this->output) {
            $this->output->text('Step 3: Creating blade connections.');
        }
        $devicesInstance
            ->create_blade_connections($jdiscToIdoitObjects);

        if ($this->output) {
            $this->output->text('Step 4: Creating module connections.');
        }
        $devicesInstance
            ->create_module_connections($jdiscToIdoitObjects, $networkInstance
                ->get_import_type_interfaces());

        if ($this->output) {
            $this->output->text('Step 5: Creating software licenses.');
        }

        if (!$this->jdiscInstance->is_jedi() && $this->jdiscInstance->check_import_software_licences()) {
            $softwareInstance
                ->create_software_licenses();
        }

        $simpleDatabaseModel = $softwareInstance->isSimpleDatabaseModel();

        // To save memory leak we iterate through all imported objects
        $counter = 1;
        // It could be possible that referenced objects have been added to the imported objects which have to be updated
        $newCount = count($jdiscToIdoitObjects);
        $cacheDatabaseLoaded = false;

        if ($this->output) {
            $this->output->text('Processing JDisc to i-doit objects...');
        }
        if ($this->progressBar && $newCount > 0) {
            $this->progressBar->setMaxSteps($newCount);
        }
        foreach ($jdiscToIdoitObjects as $deviceId => $objectId) {
            if ($this->commandQueue) {
                $this->commandQueue->processCommands();
            }
            if ($this->progressBar) {
                $this->progressBar->setMessage("Processing Object {$titleMap[$objectId]} with Object-ID #{$objectId}");
            }

            $cacheNetwork = $networkInstance
                ->load_cache($objectId);
            $cacheSoftware = $softwareInstance
                ->load_cache($objectId);
            $cacheEndpointConnectionsLoaded = $connectionEndpointInstance
                ->load_cache($objectId);

            if ($simpleDatabaseModel) {
                $cacheDatabaseLoaded = $connectionEndpointInstance
                    ->load_cache($objectId);
            }

            if ($cacheNetwork || $cacheSoftware || $cacheDatabaseLoaded || $cacheEndpointConnectionsLoaded) {
                if (!$this->jdiscInstance->is_jedi() && $this->jdiscInstance->check_import_software_licences()) {
                    // Create software license
                    $softwareInstance
                        ->handle_software_licenses($objectId, $deviceId);
                }

                // Create net listeners
                $softwareInstance
                    ->create_net_listener_connections($objectId, $deviceId);

                if ($this->jdiscInstance->isImportConnectionEndpoint()) {
                    $connectionEndpointInstance
                        ->createEndpointConnections();
                } else {
                    // Create port connections
                    $networkInstance
                        ->create_port_connections()// Update ip to port assignments
                        ->update_ip_port_assignments($objectId)// Create port map
                        ->create_port_map($objectId);
                }

                if ($simpleDatabaseModel) {
                    // @see ID-9280 Use correct instance.
                    $softwareDatabaseInstance
                        ->createDatabaseConnections($objectId, $deviceId);
                } else {
                    $softwareInstance
                        ->createSchemaTableConnections();
                }

                // Assign interfaces to the ports
                $networkInstance
                    ->adoptLocationForAttachedDevices($objectId, $deviceId)
                    ->create_network_interface_connections($objectId)// This function takes more time than the others
                    ->update_vlan_assignments($objectId, $deviceId);

                $networkInstance
                    ->updateDnsServerConnections($objectId);
            }
            if ($this->progressBar) {
                $this->progressBar->advance();
            }
        }
        if ($this->progressBar && $newCount > 0) {
            $this->progressBar->setMessage('Done!');
            $this->progressBar->finish();
        }

        $softwareInstance->setLocation(
            $this->jdiscInstance->getAllSoftwareFlag(),
            $this->jdiscInstance->getSoftwareLocationMode(),
            $this->jdiscInstance->getSoftwareLocation()
        );

        $networkInstance->setLocation(
            $this->jdiscInstance->getAllNetworkFlag(),
            $this->jdiscInstance->getNetworkLocationMode(),
            $this->jdiscInstance->getNetworkLocation()
        );

        // @see ID-7572 Update dns server on all found layer3-net objects
        $networkInstance->updateDnsServersToNetObject();

        // Remove temporary table
        $networkInstance
            ->drop_cache_table();

        /**
         * Commit all queries
         */
        $networkInstance
            ->get_database_component()
            ->commit();

        // archive outdated devices
        $tenantSettings = isys_application::instance()->container->get('settingsTenant');
        $threshold      = $tenantSettings->get('jdisc.threshold', 0);
        if ($threshold > 0) {
            $thresholdType = $tenantSettings->get('jdisc.threshold_type', 'days');

            try {
                $archiver = new Archiver($this->db, $this->jdiscInstance, $this->output);
                $archiver->setArchiveThreshold($threshold, $thresholdType);
                $archiver->archive();
            } catch (\Exception $e) {
                if ($this->output) {
                    $this->output->error([
                        "Can't archive outdated devices:",
                        $e->getMessage(),
                    ]);
                }
            }

        }
    }

}
