<?php

declare(strict_types=1);

namespace Tricks\LaravelTools\Http\Controllers;

use Composer\InstalledVersions;
use Exception;
use Illuminate\Foundation\Validation\ValidatesRequests;
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\Config;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use RuntimeException;
use Spatie\ResponseCache\Middlewares\CacheResponse;
use Tricks\LaravelTools\Data\Monitoring\EndpointData;
use Tricks\LaravelTools\Data\Monitoring\MonitoringData;
use Tricks\LaravelTools\Data\Monitoring\PackageData;
use Tricks\LaravelTools\Http\Middleware\RobotsMiddleware;

use function Sentry\captureException;

final class MonitoringController
{
    use ValidatesRequests;

    /**
     * @throws Exception
     */
    public function __invoke(): MonitoringData
    {
        $config = Config::array('laravel-tools.monitoring.packages');

        $packages = collect([
            ...$config,
            ...Arr::where(
                array: InstalledVersions::getInstalledPackages(),
                callback: fn (string $value): bool => Str::startsWith($value, 'tricks/')
            ),
        ])
            ->unique()
            ->filter(fn (mixed $item): bool => is_string($item))
            ->map(fn (string $item): PackageData => new PackageData(
                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' => $packages,
            'outdatedPackages' => $this->getOutdatedPackages(),
            'hasSentry' => ! empty(config('sentry.dsn')) && App::bound('sentry'),
            // TODO: test for real usage
            'hasRobotsMiddleware' => class_exists(RobotsMiddleware::class),
            'hasResponseCache' => class_exists(CacheResponse::class),
            '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): bool => empty($route->parameterNames()))
            ->flatten()
            ->map(fn (\Illuminate\Routing\Route $route, int $key): array => ['url' => $route->uri()])
        );
    }

    /**
     * @return array<mixed,mixed>|null
     */
    private function getOutdatedPackages(): ?array
    {
        try {
            $composer = Config::string('laravel-tools.monitoring.composer.bin');

            $composer = explode(' ', $composer);
            $command = [...$composer, 'outdated', '--direct', '--format', 'json'];
            $cwd = base_path('');
            $composer_home = config('laravel-tools.monitoring.composer.home');
            $env = is_string($composer_home)
                ? ['COMPOSER_HOME' => $composer_home]
                : [];

            $output = Process::path($cwd)
                ->env($env)
                ->run($command)
                ->throw(fn (ProcessResult $result) => throw new ProcessFailedException($result))
                ->output();

            $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;
        }

    }
}
