<?php

declare(strict_types=1);

namespace Builder\Core\Http\Controllers;

use Builder\Core\BuilderServiceProvider;
use Builder\Core\Exceptions\UnauthorizedAccessException;
use Builder\Core\Foundation\Definition\JsModule;
use Builder\Core\Foundation\Registry;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Horizon\Horizon;
use Throwable;

final class HealthController
{
    public function __construct(private readonly Registry $registry) {}

    public function __invoke(Request $request): Response
    {
        $user = $request->user();
        $permissions = $user !== null && method_exists($user, 'builderPermissions') ? $user->builderPermissions() : [];

        if (! in_array('*', $permissions, true) && ! in_array('read:health', $permissions, true)) {
            throw UnauthorizedAccessException::forPermission('read:health');
        }

        return Inertia::render('Health/Index', [
            'environment' => $this->gatherEnvironment(),
            'php' => $this->gatherPhp(),
            'server' => $this->gatherServer(),
            'database' => $this->gatherDatabase(),
            'cacheAndSession' => $this->gatherCacheAndSession(),
            'queue' => $this->gatherQueue(),
            'storage' => $this->gatherStorage(),
            'modules' => $this->gatherModules(),
        ]);
    }

    /** @return array<string, mixed> */
    private function gatherEnvironment(): array
    {
        return [
            'laravel_version' => app()->version(),
            'builder_version' => BuilderServiceProvider::CURRENT_VERSION,
            'app_env' => app()->environment(),
            'debug_mode' => (bool) config('app.debug'),
            'app_timezone' => config('app.timezone'),
            'app_locale' => config('app.locale'),
            'config_cached' => file_exists(base_path('bootstrap/cache/config.php')),
            'routes_cached' => file_exists(base_path('bootstrap/cache/routes-v7.php')),
            'events_cached' => file_exists(base_path('bootstrap/cache/events.php')),
        ];
    }

    /** @return array<string, mixed> */
    private function gatherPhp(): array
    {
        $extensions = get_loaded_extensions();
        sort($extensions);

        return [
            'version' => PHP_VERSION,
            'memory_limit' => ini_get('memory_limit'),
            'upload_max_size' => ini_get('upload_max_filesize'),
            'post_max_size' => ini_get('post_max_size'),
            'max_execution' => ini_get('max_execution_time'),
            'opcache_enabled' => (bool) ini_get('opcache.enable'),
            'extensions' => $extensions,
        ];
    }

    /** @return array<string, string> */
    private function gatherServer(): array
    {
        return [
            'software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
            'os' => php_uname('s').' '.php_uname('r'),
        ];
    }

    /** @return array<string, mixed> */
    private function gatherDatabase(): array
    {
        $connectionName = config('database.default');
        $driver = config("database.connections.{$connectionName}.driver", $connectionName);

        return [
            'driver' => $driver,
            'version' => $this->resolveDbVersion($driver),
            'name' => $this->resolveDbName($driver, $connectionName),
            'size' => $this->resolveDbSize($driver, $connectionName),
        ];
    }

    /** @return array<string, string> */
    private function gatherCacheAndSession(): array
    {
        return [
            'cache_driver' => config('cache.default'),
            'session_driver' => config('session.driver'),
        ];
    }

    /** @return array<string, mixed> */
    private function gatherQueue(): array
    {
        return [
            'driver' => config('queue.default'),
            'pending' => $this->resolvePendingJobs(),
            'failed' => $this->resolveFailedJobs(),
            'horizon' => class_exists(Horizon::class),
        ];
    }

    /** @return array<string, int> */
    private function gatherStorage(): array
    {
        return [
            'app_size' => $this->resolveDirectorySize(storage_path('app')),
            'log_size' => file_exists(storage_path('logs/laravel.log'))
                ? (int) filesize(storage_path('logs/laravel.log'))
                : 0,
        ];
    }

    /** @return list<array<string, mixed>> */
    private function gatherModules(): array
    {
        return array_map(
            fn (JsModule $module): array => [
                'key' => $module->getKey(),
                'label' => $module->getLabel() ?? $module->getKey(),
                'version' => $module->getVersion(),
                'author' => $module->getAuthor(),
                'author_url' => $module->getAuthorUrl(),
            ],
            $this->registry->all(),
        );
    }

    private function resolveDbVersion(string $driver): ?string
    {
        try {
            return match ($driver) {
                'mysql', 'mariadb' => DB::selectOne('SELECT VERSION() as version')?->version,
                'pgsql' => DB::selectOne('SELECT version() as version')?->version,
                'sqlite' => DB::selectOne('SELECT sqlite_version() as version')?->version,
                default => null,
            };
        } catch (Throwable) {
            return null;
        }
    }

    private function resolveDbName(string $driver, string $connectionName): ?string
    {
        try {
            return match ($driver) {
                'mysql', 'mariadb' => DB::selectOne('SELECT DATABASE() as name')->name
                    ?? config("database.connections.{$connectionName}.database"),
                'pgsql' => DB::selectOne('SELECT current_database() as name')->name
                    ?? config("database.connections.{$connectionName}.database"),
                'sqlite' => basename((string) config("database.connections.{$connectionName}.database")),
                default => config("database.connections.{$connectionName}.database"),
            };
        } catch (Throwable) {
            return config("database.connections.{$connectionName}.database");
        }
    }

    private function resolveDbSize(string $driver, string $connectionName): ?int
    {
        $db = (string) config("database.connections.{$connectionName}.database");

        try {
            return match ($driver) {
                'mysql', 'mariadb' => (int) (DB::selectOne(
                    'SELECT SUM(data_length + index_length) as size FROM information_schema.tables WHERE table_schema = DATABASE()',
                )->size ?? 0),
                'pgsql' => (int) (DB::selectOne('SELECT pg_database_size(current_database()) as size')->size ?? 0),
                'sqlite' => is_file($db) ? (int) filesize($db) : 0,
                default => null,
            };
        } catch (Throwable) {
            return null;
        }
    }

    private function resolvePendingJobs(): ?int
    {
        if (config('queue.default') !== 'database') {
            return null;
        }

        try {
            return DB::table('jobs')->count();
        } catch (Throwable) {
            return null;
        }
    }

    private function resolveFailedJobs(): ?int
    {
        try {
            if (! DB::getSchemaBuilder()->hasTable('failed_jobs')) {
                return null;
            }

            return DB::table('failed_jobs')->count();
        } catch (Throwable) {
            return null;
        }
    }

    private function resolveDirectorySize(string $path): int
    {
        if (! is_dir($path)) {
            return 0;
        }

        $size = 0;

        try {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
            );

            foreach ($iterator as $file) {
                if ($file->isFile()) {
                    $size += $file->getSize();
                }
            }
        } catch (Throwable) {
            // Silently ignore unreadable directories
        }

        return $size;
    }
}
