<?php

/*
 * Stats.php
 *
 * -Description-
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * @package    LibreNMS
 * @link       http://librenms.org
 * @copyright  2022 Tony Murray
 * @author     Tony Murray <murraytony@gmail.com>
 */

namespace LibreNMS\Util;

use App\Models\Callback;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class Stats
{
    public static function submit(): void
    {
        $stats = new static;

        if ($stats->isEnabled()) {
            Http::client()
                ->asForm()
                ->post(\App\Facades\LibrenmsConfig::get('callback_post'), [
                    'data' => json_encode($stats->collectData()),
                ]);
        }
    }

    public function dump(): array
    {
        return $this->collectData();
    }

    public function isEnabled(): bool
    {
        $enabled = Callback::get('enabled');

        if ($enabled == 2) {
            $this->clearStats();

            return false;
        }

        return $enabled == 1;
    }

    public function clearStats(): void
    {
        $uuid = Callback::get('uuid');

        $response = Http::client()
            ->asForm()
            ->post(\App\Facades\LibrenmsConfig::get('callback_clear'), ['uuid' => $uuid]);

        if ($response->successful()) {
            Callback::where('name', 'uuid')->delete();
            Callback::set('enabled', 0);
        }
    }

    private function collectData(): array
    {
        return [
            'uuid' => $this->getUuid(),
            'data' => $this->collectStats(),
            'info' => $this->collectDeviceInfo(),
        ];
    }

    private function getUuid(): string
    {
        $uuid = Callback::get('uuid');

        if (! $uuid) {
            $uuid = Str::uuid();
            Callback::set('uuid', $uuid);
        }

        return $uuid;
    }

    private function collectStats(): array
    {
        $version = Version::get();

        return [
            'alert_rules' => $this->selectTotal(DB::table('alert_rules')->where('disabled', 0), ['severity']),
            'alert_templates' => $this->selectTotal('alert_templates'),
            'api_tokens' => $this->selectTotal(DB::table('api_tokens')->where('disabled', 0)),
            'applications' => $this->selectTotal('applications', ['app_type']),
            'bgppeer_state' => $this->selectTotal('bgpPeers', ['bgpPeerState']),
            'bgppeer_status' => $this->selectTotal('bgpPeers', ['bgpPeerAdminStatus']),
            'bills' => $this->selectTotal('bills', ['bill_type']),
            'cef' => $this->selectTotal('cef_switching'),
            'mempool' => $this->selectTotal('mempools', ['mempool_descr']),
            'dbschema' => $this->selectStatic(DB::table('migrations')->count()),
            'snmp_version' => $this->selectTotal('devices', ['snmpver']),
            'os' => $this->selectTotal('devices', ['os']),
            'type' => $this->selectTotal('devices', ['type']),
            'hardware' => $this->selectTotal('devices', ['hardware']),
            'ipsec' => $this->selectTotal('ipsec_tunnels'),
            'ipv4_addresses' => $this->selectTotal('ipv4_addresses'),
            'ipv4_macaddress' => $this->selectTotal('ipv4_mac'),
            'ipv4_networks' => $this->selectTotal('ipv4_networks'),
            'ipv6_addresses' => $this->selectTotal('ipv6_addresses'),
            'ipv6_networks' => $this->selectTotal('ipv6_networks'),
            'xdp' => $this->selectTotal('links', ['protocol']),
            'ospf' => $this->selectTotal('ospf_instances', ['ospfVersionNumber']),
            'ospf_links' => $this->selectTotal('ospf_ports', ['ospfIfType']),
            'ospfv3' => $this->selectTotal('ospfv3_instances', ['ospfv3VersionNumber']),
            'ospfv3_links' => $this->selectTotal('ospfv3_ports', ['ospfv3IfType']),
            'arch' => $this->selectTotal('packages', ['arch']),
            'pollers' => $this->selectTotal('pollers'),
            'port_assoc' => $this->selectTotal('devices', ['port_association_mode']),
            'port_type' => $this->selectTotal('ports', ['ifType']),
            'port_ifspeed' => DB::table('ports')->select([DB::raw('COUNT(*) AS `total`'), DB::raw('ROUND(`ifSpeed`/1000/1000) as ifSpeed')])->groupBy(['ifSpeed'])->get(),
            'port_vlans' => $this->selectTotal('ports_vlans', ['state']),
            'processes' => $this->selectTotal('processes'),
            'processors' => $this->selectTotal('processors', ['processor_type']),
            'pseudowires' => $this->selectTotal('pseudowires'),
            'sensors' => $this->selectTotal('sensors', ['sensor_class']),
            'sla' => $this->selectTotal('slas', ['rtt_type']),
            'wireless' => $this->selectTotal('wireless_sensors', ['sensor_class']),
            'storage' => $this->selectTotal('storage', ['storage_type']),
            'toner' => $this->selectTotal('printer_supplies', ['supply_type']),
            'vlans' => $this->selectTotal('vlans', ['vlan_type']),
            'vminfo' => $this->selectTotal('vminfo', ['vm_type']),
            'vmware' => $this->selectTotal('vminfo'),
            'vrfs' => $this->selectTotal('vrfs'),
            'database_version' => $this->selectStatic($version->databaseServer()),
            'php_version' => $this->selectStatic(phpversion()),
            'python_version' => $this->selectStatic($version->python()),
            'rrdtool_version' => $this->selectStatic($version->rrdtool()),
            'netsnmp_version' => $this->selectStatic($version->netSnmp()),
            'os_version' => $this->selectStatic($version->os()),
            'librenms_release' => $this->selectStatic($version->release(), 'release'),
        ];
    }

    private function collectDeviceInfo(): Collection
    {
        $device_info = DB::table('devices')
            ->select([DB::raw('COUNT(*) AS `count`'), 'os', 'sysDescr', 'sysObjectID'])
            ->whereNotNull(['sysDescr', 'sysObjectID'])
            ->groupBy(['os', 'sysDescr', 'sysObjectID'])
            ->get();

        // sanitize sysDescr
        return $device_info->map(function ($entry) {
            // remove hostnames from linux, macosx, and SunOS
            $entry->sysDescr = preg_replace_callback('/^(Linux |Darwin |FreeBSD |SunOS )[A-Za-z0-9._\-]+ ([0-9.]{3,9})/', fn ($matches) => $matches[1] . 'hostname ' . $matches[2], (string) $entry->sysDescr);

            // wipe serial numbers, preserve the format
            $sn_patterns = ['/[A-Z]/', '/[a-z]/', '/[0-9]/'];
            $sn_replacements = ['A', 'a', '0'];
            $entry->sysDescr = preg_replace_callback(
                '/((s\/?n|serial num(ber)?)[:=]? ?)([a-z0-9.\-]{4,16})/i',
                fn ($matches) => $matches[1] . preg_replace($sn_patterns, $sn_replacements, $matches[4]),
                (string) $entry->sysDescr
            );

            return $entry;
        });
    }

    /**
     * @param  Builder|string  $table
     * @param  array  $groups
     * @return Collection
     */
    private function selectTotal($table, array $groups = []): Collection
    {
        $query = $table instanceof Builder ? $table : DB::table($table);

        if (! empty($groups)) {
            $query->groupBy($groups);
        }

        return $query
            ->select(array_merge([DB::raw('COUNT(*) AS `total`')], $groups))
            ->get();
    }

    /**
     * @param  string|int|float  $value
     * @param  string  $name
     * @return array[]
     */
    private function selectStatic($value, string $name = 'version'): array
    {
        return [['total' => 1, $name => $value]];
    }
}
