<?php

/**
 * GraphController.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 <https://www.gnu.org/licenses/>.
 *
 * @link       https://www.librenms.org
 *
 * @copyright  2018 Tony Murray
 * @author     Tony Murray <murraytony@gmail.com>
 */

namespace App\Http\Controllers\Widgets;

use App\Facades\LibrenmsConfig;
use App\Models\Application;
use App\Models\Bill;
use App\Models\Customoid;
use App\Models\Device;
use App\Models\MuninPlugin;
use App\Models\Port;
use App\Models\Sensor;
use App\Models\Service;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\View\View;
use LibreNMS\Util\Graph;
use LibreNMS\Util\Time;

class GraphController extends WidgetController
{
    protected string $name = 'generic-graph';
    protected $defaults = [
        'title' => null,
        'refresh' => 60,
        'graph_type' => null,
        'graph_range' => 'oneday',
        'graph_legend' => 'yes',
        'graph_device' => null,
        'graph_port' => null,
        'graph_application' => null,
        'graph_munin' => null,
        'graph_service' => null,
        'graph_customoid' => null,
        'graph_ports' => [],
        'graph_sensors' => [],
        'graph_stack' => 'no',
        'graph_custom' => [],
        'graph_manual' => null,
        'graph_bill' => null,
    ];

    public function getTitle(): string
    {
        $settings = $this->getSettings();

        if (! empty($settings['title'])) {
            return $settings['title'];
        }

        // automatic title
        $type = $this->getGraphType();
        if ($type == 'device') {
            $device = Device::find($settings['graph_device']);

            return ($device ? $device->displayName() : 'Device') . ' / ' . $settings['graph_type'];
        } elseif ($type == 'aggregate') {
            $aggregate_type = $this->getGraphType(false);
            if ($aggregate_type == 'ports') {
                return 'Overall ' . $this->getGraphType(false) . ' Bits (' . $settings['graph_range'] . ')';
            } else {
                return 'Overall ' . $this->getGraphType(false) . ' Aggregate (' . $settings['graph_range'] . ')';
            }
        } elseif ($type == 'port') {
            if ($port = Port::find($settings['graph_port'])) {
                return $port->device->displayName() . ' / ' . $port->getShortLabel() . ' / ' . $settings['graph_type'];
            }
        } elseif ($type == 'application') {
            if ($application = Application::find($settings['graph_application'])) {
                return $application->device->displayName() . ' / ' . $application->app_type . ' / ' . $settings['graph_type'];
            }
        } elseif ($type == 'bill') {
            if ($bill = Bill::find($settings['graph_bill'])) {
                return 'Bill: ' . $bill->bill_name;
            }
        } elseif ($type == 'munin') {
            if ($munin = MuninPlugin::find($settings['graph_munin'])) {
                return $munin->device->displayName() . ' / ' . $munin->mplug_type . ' / ' . $settings['graph_type'];
            }
        } elseif ($type == 'service') {
            if ($service = Service::find($settings['graph_service'])) {
                return $service->device->displayName() . ' / ' . $service->service_type . ' (' . $service->service_desc . ')' . ' / ' . $settings['graph_type'];
            }
        } elseif ($type == 'customoid') {
            if ($customoid = Customoid::find($settings['graph_customoid'])) {
                return $customoid->device?->displayName() . ' / ' . $type . ' / ' . $customoid->customoid_descr;
            }
        }

        // fall back for types where we couldn't find the item
        if ($settings['graph_type']) {
            return 'Device / ' . ucfirst((string) $type) . ' / ' . $settings['graph_type'];
        }

        return parent::getTitle();
    }

    public function getSettingsView(Request $request): View
    {
        $data = $this->getSettings(true);

        // format display name for selected graph type
        $type_parts = explode('_', (string) $data['graph_type']);
        $primary = array_shift($type_parts);
        $secondary = implode('_', $type_parts);
        $name = $primary . ' ' . (Graph::isMibGraph($primary, $secondary) ? $secondary : implode(' ', $type_parts));

        // format display for selected items
        if ($primary == 'device' && $data['graph_device']) {
            $device = Device::find($data['graph_device']);
        }
        $data['device_text'] = isset($device) ? $device->displayName() : __('Device does not exist');

        if ($primary == 'port' && $data['graph_port']) {
            $port = Port::find($data['graph_port']);
        }
        $data['port_text'] = isset($port) ? $port->getLabel() : __('Port does not exist');

        if ($primary == 'application' && $data['graph_application']) {
            $app = Application::find($data['graph_application']);
        }
        $data['application_text'] = isset($app) ? $app->displayName() . ' - ' . $app->device->displayName() : __('App does not exist');

        if ($primary == 'bill' && $data['graph_bill']) {
            $bill = Bill::find($data['graph_bill']);
        }
        $data['bill_text'] = isset($bill) ? $bill->bill_name : __('Bill does not exist');

        if ($primary == 'munin' && $data['graph_munin']) {
            $mplug = MuninPlugin::with('device')->find($data['graph_munin']);
        }
        $data['munin_text'] = isset($mplug) ? $mplug->device->displayName() . ' - ' . $mplug->mplug_type : __('Munin plugin does not exist');

        if ($primary == 'service' && $data['graph_service']) {
            $service = Service::with('device')->find($data['graph_service']);
        }
        $data['service_text'] = isset($service) ? $service->device->displayName() . ' - ' . $service->service_type . ' (' . $service->service_desc . ')' : __('Service does not exist');

        if ($primary == 'customoid' && $data['graph_customoid']) {
            $customoid = Customoid::with('device')->find($data['graph_customoid']);
        }
        $data['customoid_text'] = isset($customoid) ? $customoid->device?->displayName() . ' - ' . $customoid->customoid_descr : __('Custom OID does not exist');

        $data['graph_ports'] = Port::whereIntegerInRaw('port_id', $data['graph_ports'])
            ->select('ports.device_id', 'port_id', 'ifAlias', 'ifName', 'ifDescr')
            ->with(['device' => function ($query): void {
                $query->select('device_id', 'hostname', 'sysName', 'display');
            }])->get();

        $data['graph_port_ids'] = $data['graph_ports']->pluck('port_id')->toJson();

        $data['graph_sensors'] = Sensor::whereIntegerInRaw('sensor_id', $data['graph_sensors'])
            ->select('sensors.device_id', 'sensor_id', 'sensor_class', 'sensor_descr', 'sensor_type')
            ->with(['device' => function ($query): void {
                $query->select('device_id', 'hostname', 'sysName', 'display');
            }])->get();

        $data['graph_sensor_ids'] = $data['graph_sensors']->pluck('sensor_id')->toJson();

        $data['graph_text'] = ucwords($name);

        return view('widgets.settings.graph', $data);
    }

    /**
     * @param  Request  $request
     * @return View
     */
    public function getView(Request $request): string|View
    {
        $settings = $this->getSettings();

        // force settings if not initialized
        if ($this->hasInvalidSettings()) {
            return $this->getSettingsView($request);
        }

        $type = $this->getGraphType();
        $params = [];

        if ($type == 'device') {
            $params[] = 'device=' . $settings['graph_device'];
        } elseif ($type == 'application') {
            $params[] = 'id=' . $settings['graph_application'];
        } elseif ($type == 'munin') {
            if ($mplug = MuninPlugin::find($settings['graph_munin'])) {
                $params[] = 'device=' . $mplug->device_id;
                $params[] = 'plugin=' . $mplug->mplug_type;
            }
        } elseif ($type == 'service') {
            if ($service = Service::find($settings['graph_service'])) {
                $params[] = 'device=' . $service->device_id;
                $params[] = 'id=' . $service->service_id;
            }
        } elseif ($type == 'customoid') {
            if ($customoid = Customoid::find($settings['graph_customoid'])) {
                $params[] = 'device=' . $customoid->device_id;
                $params[] = 'id=' . $customoid->customoid_id;
                $settings['graph_type'] = 'customoid_' . $customoid->customoid_descr;
            }
        } elseif ($type == 'aggregate') {
            $aggregate_type = $this->getGraphType(false);
            if ($aggregate_type == 'custom') {
                $aggregate_type = $settings['graph_custom'];
            }

            if ($aggregate_type == 'sensors') {
                // Multi-sensor graph
                $sensor_ids = array_filter($settings['graph_sensors']); // Filter empty values
                $params[] = 'id=' . implode(',', $sensor_ids);
                if (($settings['graph_stack'] ?? 'no') == 'yes') {
                    $params[] = 'stack=1';
                }
                $settings['graph_type'] = 'multisensor_graph';
            } elseif ($aggregate_type == 'ports') {
                $port_ids = $settings['graph_ports'];
                $params[] = 'id=' . implode(',', $port_ids);
                $settings['graph_type'] = 'multiport_bits_separate';
            } else {
                $port_types = collect((array) $aggregate_type)->map(function ($type) {
                    // check for config definitions
                    if (LibrenmsConfig::has("{$type}_descr")) {
                        return LibrenmsConfig::get("{$type}_descr", []);
                    }

                    return $type;
                })->flatten();

                $port_ids = Port::hasAccess($request->user())->where(function ($query) use ($port_types): void {
                    foreach ($port_types as $port_type) {
                        $port_type = str_replace('@', '%', $port_type);
                        $query->orWhere('port_descr_type', 'LIKE', $port_type);
                    }
                })->pluck('port_id')->all();
                $params[] = 'id=' . implode(',', $port_ids);
                $settings['graph_type'] = 'multiport_bits_separate';
            }
        } else {
            $params[] = 'id=' . $settings['graph_' . $type];
        }

        $data = $settings;
        $data['params'] = $params;
        $data['dimensions'] = $request->input('dimensions');
        $data['from'] = Carbon::now()->subSeconds(Time::legacyTimeSpecToSecs($settings['graph_range'] ?? ''))->timestamp;
        $data['to'] = Carbon::now()->timestamp;

        return view('widgets.graph', $data);
    }

    private function getGraphType($summarize = true)
    {
        $graph_type = $this->getSettings()['graph_type'];

        if (empty($graph_type)) {
            return $graph_type;
        }

        $type = explode('_', (string) $graph_type, 2)[0];

        if ($summarize && in_array($type, ['transit', 'peering', 'core', 'ports', 'sensors', 'custom'])) {
            return 'aggregate';
        }

        return $type;
    }

    private function hasInvalidSettings()
    {
        $raw_type = $this->getGraphType(false);
        if (in_array($raw_type, ['custom', 'ports', 'sensors']) || $this->getGraphType() != 'aggregate') {
            return empty($this->getSettings()['graph_' . $raw_type]);
        }

        return false; // non-custom aggregate types require no additional settings
    }

    public function getSettings($settingsView = false): array
    {
        if (is_null($this->settings)) {
            $settings = parent::getSettings($settingsView);

            // legacy data conversions
            if ($settings['graph_type'] == 'manual') {
                $settings['graph_type'] = 'custom';
                $settings['graph_custom'] = explode(',', (string) $settings['graph_manual']);
            }
            if ($settings['graph_type'] == 'transpeer') {
                $settings['graph_type'] = 'custom';
                $settings['graph_custom'] = ['transit', 'peer'];
            }

            $settings['graph_device'] = $this->convertLegacySettingId($settings['graph_device'], 'device_id');
            $settings['graph_port'] = $this->convertLegacySettingId($settings['graph_port'], 'port_id');
            $settings['graph_application'] = $this->convertLegacySettingId($settings['graph_application'], 'app_id');
            $settings['graph_munin'] = $this->convertLegacySettingId($settings['graph_munin'], 'mplug_id');
            $settings['graph_service'] = $this->convertLegacySettingId($settings['graph_service'], 'service_id');
            $settings['graph_bill'] = $this->convertLegacySettingId($settings['graph_bill'], 'bill_id');

            $settings['graph_custom'] = (array) $settings['graph_custom'];
            $settings['graph_ports'] = (array) $settings['graph_ports'];
            $settings['graph_sensors'] = array_filter((array) $settings['graph_sensors']);

            $this->settings = $settings;
        }

        return $this->settings;
    }

    private function convertLegacySettingId($setting, $key)
    {
        if ($setting && ! is_numeric($setting)) {
            $data = json_decode((string) $setting, true);

            return $data[$key] ?? 0;
        }

        return $setting;
    }
}
