<?php

/**
 * AlertRules.php
 *
 * Extending the built in logging to add an event logger function
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Original Code:
 *
 * @author Daniel Preussker <f0o@devilcode.org>
 * @copyright 2014 f0o, LibreNMS
 * @license GPL
 *
 * @link       https://www.librenms.org
 *
 * @copyright  2019 KanREN, Inc.
 * @author     Heath Barnhart <hbarnhart@kanren.net>
 */

namespace LibreNMS\Alert;

use App\Models\Eventlog;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use LibreNMS\Alerting\QueryBuilderParser;
use LibreNMS\Enum\AlertState;
use LibreNMS\Enum\MaintenanceStatus;
use LibreNMS\Enum\Severity;
use PDO;
use PDOException;

class AlertRules
{
    public function runRules($device_id)
    {
        //Check to see if under maintenance
        if (AlertUtil::getMaintenanceStatus($device_id) === MaintenanceStatus::SKIP_ALERTS) {
            echo "Under Maintenance, skipping alert rules check.\r\n";

            return false;
        }
        //Check to see if disable alerting is set
        if (AlertUtil::hasDisableNotify($device_id)) {
            echo "Disable alerting is set, Clearing active alerts and skipping alert rules check\r\n";
            $device_alert['state'] = AlertState::CLEAR;
            $device_alert['alerted'] = 0;
            $device_alert['open'] = 0;
            dbUpdate($device_alert, 'alerts', '`device_id` = ?', [$device_id]);

            return false;
        }
        //Checks each rule.
        foreach (AlertUtil::getRules($device_id) as $rule) {
            Log::info('Rule %p#' . $rule['id'] . ' (' . $rule['name'] . '):%n ', ['color' => true]);
            $extra = json_decode((string) $rule['extra'], true);
            if (isset($extra['invert'])) {
                $inv = (bool) $extra['invert'];
            } else {
                $inv = false;
            }
            d_echo(PHP_EOL);
            if (empty($rule['query'])) {
                $rule['query'] = QueryBuilderParser::fromJson($rule['builder'])->toSql();
            }
            $sql = $rule['query'];

            // set fetch assoc
            try {
                $query = DB::connection()->getPdo()->prepare($sql);
                $query->execute([$device_id]);

                $qry = $query->fetchAll(PDO::FETCH_ASSOC);
            } catch (PDOException $e) {
                c_echo('%RError: %n' . $e->getMessage() . PHP_EOL);
                Eventlog::log("Error in alert rule {$rule['name']} ({$rule['id']}): " . $e->getMessage(), $device_id, 'alert', Severity::Error);
                continue; // skip this rule
            }

            $cnt = count($qry);
            for ($i = 0; $i < $cnt; $i++) {
                if (isset($qry[$i]['ip'])) {
                    $qry[$i]['ip'] = inet6_ntop($qry[$i]['ip']);
                }
            }
            $s = count($qry);
            if ($s == 0 && $inv === false) {
                $doalert = false;
            } elseif ($s > 0 && $inv === false) {
                $doalert = true;
            } elseif ($s == 0 && $inv === true) {
                $doalert = true;
            } else {
                $doalert = false;
            }

            $current_state = dbFetchCell('SELECT state FROM alerts WHERE rule_id = ? AND device_id = ? ORDER BY id DESC LIMIT 1', [$rule['id'], $device_id]);
            if ($doalert) {
                if ($current_state == AlertState::ACKNOWLEDGED) {
                    Log::info('Status: %ySKIP%n', ['color' => true]);
                } elseif ($current_state >= AlertState::ACTIVE) {
                    Log::info('Status: %bNOCHG%n', ['color' => true]);
                    // NOCHG here doesn't mean no change full stop. It means no change to the alert state
                    // So we update the details column with any fresh changes to the alert output we might have.
                    $alert_log = dbFetchRow('SELECT alert_log.id, alert_log.details FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0
     ORDER BY alert_log.id DESC LIMIT 1', [$device_id, $rule['id']]);
                    $details = [];
                    if (! empty($alert_log['details'])) {
                        $details = json_decode(gzuncompress($alert_log['details']), true);
                    }
                    $details['contacts'] = AlertUtil::getContacts($qry);
                    $details['rule'] = $qry;
                    $details = gzcompress(json_encode($details), 9);
                    dbUpdate(['details' => $details], 'alert_log', 'id = ?', [$alert_log['id']]);
                } else {
                    $extra = gzcompress(json_encode(['contacts' => AlertUtil::getContacts($qry), 'rule' => $qry]), 9);
                    if (dbInsert(['state' => AlertState::ACTIVE, 'device_id' => $device_id, 'rule_id' => $rule['id'], 'details' => $extra], 'alert_log')) {
                        if (is_null($current_state)) {
                            dbInsert(['state' => AlertState::ACTIVE, 'device_id' => $device_id, 'rule_id' => $rule['id'], 'open' => 1, 'alerted' => 0], 'alerts');
                        } else {
                            dbUpdate(['state' => AlertState::ACTIVE, 'open' => 1, 'timestamp' => Carbon::now()], 'alerts', 'device_id = ? && rule_id = ?', [$device_id, $rule['id']]);
                        }
                        Log::info(PHP_EOL . 'Status: %rALERT%n', ['color' => true]);
                    }
                }
            } else {
                if (! is_null($current_state) && $current_state == AlertState::RECOVERED) {
                    Log::info('Status: %bNOCHG%n', ['color' => true]);
                } else {
                    if (dbInsert(['state' => AlertState::RECOVERED, 'device_id' => $device_id, 'rule_id' => $rule['id']], 'alert_log')) {
                        if (is_null($current_state)) {
                            dbInsert(['state' => AlertState::RECOVERED, 'device_id' => $device_id, 'rule_id' => $rule['id'], 'open' => 1, 'alerted' => 0], 'alerts');
                        } else {
                            dbUpdate(['state' => AlertState::RECOVERED, 'open' => 1, 'note' => '', 'timestamp' => Carbon::now()], 'alerts', 'device_id = ? && rule_id = ?', [$device_id, $rule['id']]);
                        }

                        Log::info(PHP_EOL . 'Status: %gOK%n', ['color' => true]);
                    }
                }
            }
        }
    }
}
