<?php

namespace Tricks\Monitoring\Http\Controllers;

use Composer\InstalledVersions;
use Exception;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Process\Exceptions\ProcessFailedException;
use Illuminate\Process\ProcessResult;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use RuntimeException;
use Spatie\LaravelData\DataCollection;
use Symfony\Component\Process\Process as SymfonyProcess;
use Tricks\Monitoring\Data\EndpointData;
use Tricks\Monitoring\Data\MonitoringData;
use Tricks\Monitoring\Data\PackageData;
use function Sentry\captureException;

class MonitoringController
{
    use ValidatesRequests;

    /**
     * @throws Exception
     */
    public function __invoke(Request $request): MonitoringData
    {
        $config = config('laravel-monitoring.packages');
        if (! is_array($config)) {
            throw new Exception('Error: laravel-monitoring.packages is not an array');
        }
        $packages = collect([
            ...$config,
            ...Arr::where(InstalledVersions::getInstalledPackages(),
                fn (string $value) => Str::startsWith($value, 'tricks/'))
        ])
            ->unique()
            ->map(fn (string $item) => [
                'name' => $item,
                'version' => InstalledVersions::isInstalled($item) ? InstalledVersions::getVersion($item) : false
            ]);


        return MonitoringData::from([
            'php' => PHP_VERSION,
            'opcacheDisabled' => ! function_exists('opcache_get_status') || opcache_get_status() === false,
            'packages' => PackageData::collect($packages),
            'outdatedPackages' => self::getOutdatedPackages(),
            'hasSentry' => ! empty(config('sentry.dsn')) && App::bound('sentry'),
            'hasRobotsMiddleware' => class_exists('App\Http\Middleware\RobotsMiddleware'),
            'hasResponseCache' => class_exists('Spatie\ResponseCache\Middlewares\CacheResponse'),
            'endpoints' => self::getEndpoints(),
            'debugModeDisabled' => App::hasDebugModeEnabled() === false,
            'hasMailConfig' => ! empty(config('mail.mailers.smtp.username')),
            'mailUsesCampaignMonitor' => config('mail.mailers.smtp.host') === 'smtp.api.createsend.com',
        ]);
    }

    /**
     * @return Collection<int, EndpointData>
     */
    public static function getEndpoints(): Collection
    {
        return EndpointData::collect(collect(Route::getRoutes()->getRoutesByName())
            ->filter(fn (\Illuminate\Routing\Route $route, string $name) => Str::contains($name, 'endpoints.'))
            ->filter(fn (\Illuminate\Routing\Route $route, string $name) => empty($route->parameterNames()))
            ->flatten()
            ->map(fn (\Illuminate\Routing\Route $route, int $key) => ['url' => $route->uri()])
        );
    }

    /**
     * @return array<int,mixed>|null
     */
    private static function getOutdatedPackages(): ?array
    {
        try {
            $composer = config('laravel-monitoring.composer.bin');
            if (! is_string($composer)) {
                throw new RuntimeException('laravel-monitoring.composer.bin is not a string');
            }
            $composer = explode(' ', $composer);
            $command = [...$composer, 'outdated', '--direct', '--format', 'json'];
            $cwd = base_path('');
            $composer_home = config('laravel-monitoring.composer.home');
            $env = is_string($composer_home)
                ? ['COMPOSER_HOME' => $composer_home]
                : [];

            if (class_exists(Process::class)) {
                $output = Process::path($cwd)
                    ->env($env)
                    ->run($command)
                    ->throw(fn (ProcessResult $result) => throw new ProcessFailedException($result))
                    ->output();
            } else {
                /**
                 * @TODO Remove when no project uses L<10
                 */
                $output = (new SymfonyProcess($command))
                    ->setEnv($env)
                    ->setWorkingDirectory($cwd)
                    ->mustRun()
                    ->getOutput();
            }
            $packages = json_decode(
                $output,
                true,
                flags: JSON_THROW_ON_ERROR
            );
            if (! is_array($packages)) {
                throw new RuntimeException('outdatedPackages could not correctly parse json output');
            }
            return $packages;
        } catch (Exception $exception) {
            captureException($exception);
            return null;
        }

    }
}
