<?php

class ComplianceImport
{
    private $rulesModel;
    private $reportModel;
    private $userName;
    private $inventoryVars;
    private $duplicateItems = ['conditions' => [], 'reports' => []];

    public function __construct(
        rules_model $rulesModel,
        Search_save_model $reportModel,
        array $inventoryVars,
        string $userName,
    ) {
        $this->rulesModel = $rulesModel;
        $this->reportModel = $reportModel;
        $this->userName = $userName;
        $this->inventoryVars = $inventoryVars;
    }

    public function import(array $data, bool $overwrite, bool $public, string $source = '')
    {
        if (!isset($data['reports']) || empty($data['reports']) || ![$data['reports']]) {
            throw new Exception('Import data missing reports');
        }

        if (!isset($data['conditions']) || empty($data['conditions']) || ![$data['conditions']]) {
            throw new Exception('Import data missing conditions');
        }

        $reportConditions = array_reduce(
            $data['reports'],
            function ($acc, $r) {
                return array_merge($acc, $r['conditions']);
            },
            [],
        );
        $filteredConditions = array_filter($data['conditions'], function ($cond) use ($reportConditions) {
            return in_array($cond['id'], $reportConditions);
        });

        $processedConditions = $this->processConditions($filteredConditions, $overwrite) ?? [];
        $processedReports =
            $this->processReports($data['reports'], $processedConditions, $overwrite, $public, $source) ?? [];
        $return = [
            'processed-conditions' => $processedConditions,
            'processed-reports' => $processedReports,
        ];
        if (!empty($this->duplicateItems['conditions']) || !empty($this->duplicateItems['reports'])) {
            $return['duplicates'] = $this->duplicateItems;
        }
        return $return;
    }

    private function processConditions(array $conditions, $overwrite)
    {
        return array_reduce(
            $conditions,
            function ($reducer, $condition) use ($overwrite) {
                $this->validateCondition($condition);
                $ruleToSave = [
                    'username' => $this->userName,
                    'name' => $condition['name'],
                    'description' => $condition['description'],
                    'type' => $condition['type'],
                    'category' => $condition['category'],
                    'conditionMustBeMet' => $condition['condition_for'] == 'passing' ? 'true' : 'false',
                    'severity' => $condition['severity'],
                    'hostcontexts' => json_encode(
                        $condition['host_filter']
                            ? ['name' => $condition['host_filter'], 'includes' => [$condition['host_filter']]]
                            : ['name' => 'All hosts', 'includes' => []],
                    ),
                    'export_id' => $condition['id'],
                    'rules' => $condition['rules'],
                ];

                $setter = $this->getConditionSetter($condition['type']);
                $setter->setCondition($ruleToSave, $this->inventoryVars);
                unset($ruleToSave['rules']);
                try {
                    if ($savedRule = $this->rulesModel->saverule($ruleToSave, $overwrite)) {
                        $reducer[$condition['id']] = $savedRule->id;
                        return $reducer;
                    } else {
                        $this->duplicateItems['conditions'][] = $ruleToSave['name'];
                    }
                } catch (Exception $e) {
                    // 23505 error - unique_violation
                    if (strstr($e->getMessage(), 'SQLSTATE[23505]')) {
                        $this->duplicateItems['conditions'][] = $ruleToSave['name'];
                    } else {
                        throw $e;
                    }
                }
            },
            [],
        );
    }

    private function validateCondition($condition)
    {
        $required = ['name', 'type', 'category', 'condition_for', 'severity', 'id', 'rules'];
        $missing = array_diff($required, array_keys($condition));
        if (sizeof($missing) > 0) {
            throw new Exception('Required condition fields are missing: ' . implode(', ', $missing));
        }
    }

    private function getConditionSetter(string $type)
    {
        $class = ucfirst($type) . 'ConditionSetter';
        $file = __DIR__ . '/implementations/' . $class . '.php';
        if (file_exists($file) && (include_once $file)) {
            return new $class();
        } else {
            throw new Exception('Class ' . $class . ' not found.');
        }
    }

    private function processReports(array $reports, array $processedConditions, $overwrite, $public, $source)
    {
        return array_reduce(
            $reports,
            function ($reducer, $report) use ($processedConditions, $overwrite, $public, $source) {
                $this->validateReport($report);
                $reportToSave = [
                    'username' => $this->userName,
                    'export_id' => $report['id'],
                    'type' => $report['type'],
                    'label' => $report['title'],
                    'url' => 'advancedreports',
                    'reportType' => 'Advanced reports',
                    'reportCategory' => 'compliance_report',
                    'params' => '',
                    'date' => time(),
                    'is_public' => $public,
                    'advancedreportsdata' => [
                        'SQL' => [
                            'SQLSTRING' => '',
                            'conditions' => array_reduce(
                                $report['conditions'],
                                function ($reducer, $condition) use ($processedConditions) {
                                    if (isset($processedConditions[$condition])) {
                                        $reducer[] = $processedConditions[$condition];
                                    }
                                    return $reducer;
                                },
                                [],
                            ),
                        ],
                    ],
                    'meta_data' => '{"source": "' . addslashes($source) . '"}',
                ];

                try {
                    if ($savedReport = $this->reportModel->insert($reportToSave, $overwrite)) {
                        $reducer[$report['id']] = $savedReport->id;
                        return $reducer;
                    } else {
                        $this->duplicateItems['reports'][] = $reportToSave['label'];
                    }
                } catch (Exception $e) {
                    // 23505 error - unique_violation
                    if (strstr($e->getMessage(), 'SQLSTATE[23505]')) {
                        $this->duplicateItems['reports'][] = $reportToSave['label'];
                    } else {
                        throw $e;
                    }
                }
            },
            [],
        );
    }

    private function validateReport($report)
    {
        $required = ['id', 'type', 'title', 'conditions'];
        $missing = array_diff($required, array_keys($report));
        if (sizeof($missing) > 0) {
            throw new Exception('Required report fields are missing: ' . implode(', ', $missing));
        }
    }
}
