<?php

namespace App\Services\System;

use App\Models\ImportedJob;
use App\Models\User;
use App\Services\Installer\InstallerService;
use App\Services\Settings\SettingsService;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Throwable;

class SystemHealthService
{
    public const SCHEDULER_HEARTBEAT_CACHE_KEY = 'remotematch.scheduler.last_heartbeat_at';

    public function __construct(
        private readonly SettingsService $settings,
        private readonly InstallerService $installer,
    ) {}

    /**
     * @return array<int, array{key: string, group: string, status: string, title: string, details: string, action: string|null}>
     */
    public function checks(): array
    {
        return [
            $this->appEnvironment(),
            $this->appUrl(),
            $this->databaseConnection(),
            $this->writablePath('storage', storage_path(), 'Storage directory'),
            $this->writablePath('framework-cache', storage_path('framework'), 'Framework cache directory'),
            $this->writablePath('bootstrap-cache', base_path('bootstrap/cache'), 'Bootstrap cache directory'),
            $this->publicStorageLink(),
            $this->queueTables(),
            $this->schedulerHeartbeat(),
            $this->defaultAdminPassword(),
            $this->thirdPartyCredentials(
                enabledKey: 'openai.enabled',
                keys: ['openai.api_key'],
                title: 'OpenAI configuration',
                action: 'Save the OpenAI API key under Admin > Settings > OpenAI / AI.'
            ),
            $this->thirdPartyCredentials(
                enabledKey: 'payment.razorpay_enabled',
                keys: ['payment.razorpay_key_id', 'payment.razorpay_key_secret', 'payment.razorpay_webhook_secret'],
                title: 'Razorpay configuration',
                action: 'Save Razorpay key ID, key secret, and webhook secret under Admin > Settings > Payment.'
            ),
            $this->thirdPartyCredentials(
                enabledKey: 'google.login_enabled',
                keys: ['google.client_id', 'google.client_secret', 'google.redirect_url'],
                title: 'Google sign-in configuration',
                action: 'Save Google OAuth credentials and make the redirect URL match Google Cloud Console.'
            ),
            $this->thirdPartyCredentials(
                enabledKey: 'email.brevo_enabled',
                keys: ['email.brevo_api_key', 'email.from_email', 'email.from_name'],
                title: 'Brevo transactional email',
                action: 'Save Brevo API key and verified sender details under Admin > Settings > Email.'
            ),
            $this->importedJobs(),
            $this->installationLock(),
            $this->maintenanceMode(),
            $this->demoMode(),
        ];
    }

    /**
     * @return array{pass: int, warning: int, fail: int, total: int}
     */
    public function summary(): array
    {
        $checks = collect($this->checks());

        return [
            'pass' => $checks->where('status', 'pass')->count(),
            'warning' => $checks->where('status', 'warning')->count(),
            'fail' => $checks->where('status', 'fail')->count(),
            'total' => $checks->count(),
        ];
    }

    public function recordSchedulerHeartbeat(): void
    {
        Cache::put(self::SCHEDULER_HEARTBEAT_CACHE_KEY, now()->toIso8601String(), now()->addDays(2));
    }

    /**
     * @return array{key: string, group: string, status: string, title: string, details: string, action: string|null}
     */
    private function appEnvironment(): array
    {
        if (app()->environment('production') && config('app.debug')) {
            return $this->fail(
                'app-environment',
                'Core',
                'Debug mode is enabled in production',
                'APP_ENV is production, but APP_DEBUG is true.',
                'Set APP_DEBUG=false in .env and run php artisan optimize:clear.'
            );
        }

        if (! app()->environment('production')) {
            return $this->warning(
                'app-environment',
                'Core',
                'Application is not in production mode',
                'Current APP_ENV is '.app()->environment().'. This is acceptable locally, but not for a live buyer install.',
                'Set APP_ENV=production on the live server.'
            );
        }

        return $this->pass('app-environment', 'Core', 'Production environment is configured', 'APP_ENV and APP_DEBUG are safe for production.');
    }

    private function appUrl(): array
    {
        $url = (string) config('app.url');

        if ($url === '') {
            return $this->fail('app-url', 'Core', 'APP_URL is missing', 'The application URL is required for OAuth, payments, emails, and signed links.', 'Set APP_URL to the public HTTPS domain.');
        }

        if (str_contains($url, 'localhost') || str_contains($url, '127.0.0.1')) {
            return $this->warning('app-url', 'Core', 'APP_URL is local', "Current APP_URL is {$url}.", 'Use the final public domain before enabling Google OAuth, Razorpay, and Brevo.');
        }

        if (! str_starts_with($url, 'https://') && app()->environment('production')) {
            return $this->fail('app-url', 'Core', 'Production APP_URL must use HTTPS', "Current APP_URL is {$url}.", 'Install SSL and set APP_URL=https://your-domain.com.');
        }

        return $this->pass('app-url', 'Core', 'APP_URL is configured', $url);
    }

    private function databaseConnection(): array
    {
        try {
            DB::connection()->getPdo();
            DB::select('select 1');
        } catch (Throwable $exception) {
            return $this->fail('database', 'Database', 'Database connection failed', $exception->getMessage(), 'Check DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, and DB_PASSWORD.');
        }

        return $this->pass('database', 'Database', 'Database connection is working', 'The application can connect to the configured database.');
    }

    private function writablePath(string $key, string $path, string $title): array
    {
        if (! File::exists($path)) {
            return $this->fail($key, 'Filesystem', "{$title} is missing", $path, 'Create the directory and make it writable by PHP.');
        }

        if (! is_writable($path)) {
            return $this->fail($key, 'Filesystem', "{$title} is not writable", $path, 'Fix ownership or permissions on the server.');
        }

        return $this->pass($key, 'Filesystem', "{$title} is writable", $path);
    }

    private function publicStorageLink(): array
    {
        $path = public_path('storage');

        if (is_link($path) || is_dir($path)) {
            return $this->pass('public-storage', 'Filesystem', 'Public storage is available', 'Uploaded logos, favicons, and public assets can be served.');
        }

        return $this->warning('public-storage', 'Filesystem', 'Public storage link is missing', 'public/storage was not found.', 'Run php artisan storage:link, or configure cPanel public storage manually if symlinks are disabled.');
    }

    private function queueTables(): array
    {
        if (config('queue.default') !== 'database') {
            return $this->warning('queue', 'Queue', 'Queue is not using the database driver', 'Current QUEUE_CONNECTION is '.(string) config('queue.default').'.', 'Database queue is recommended for shared hosting until a stronger worker is configured.');
        }

        try {
            $hasTables = Schema::hasTable('jobs') && Schema::hasTable('failed_jobs');
        } catch (Throwable $exception) {
            return $this->fail('queue', 'Queue', 'Unable to inspect queue tables', $exception->getMessage(), 'Fix the database connection first.');
        }

        if (! $hasTables) {
            return $this->fail('queue', 'Queue', 'Database queue tables are missing', 'The jobs and failed_jobs tables are required.', 'Run php artisan migrate.');
        }

        $failedJobs = DB::table('failed_jobs')->count();

        if ($failedJobs > 0) {
            return $this->warning('queue', 'Queue', 'Queue tables exist with failed jobs', "{$failedJobs} failed job(s) found.", 'Review failed jobs in logs and retry or clear after fixing the cause.');
        }

        return $this->pass('queue', 'Queue', 'Database queue is ready', 'jobs and failed_jobs tables are present.');
    }

    private function schedulerHeartbeat(): array
    {
        try {
            $heartbeat = Cache::get(self::SCHEDULER_HEARTBEAT_CACHE_KEY);
        } catch (Throwable $exception) {
            return $this->warning('scheduler', 'Scheduler', 'Unable to read scheduler heartbeat', $exception->getMessage(), 'Fix the cache/database connection and confirm scheduler cron is running.');
        }

        if (! is_string($heartbeat) || $heartbeat === '') {
            return $this->warning('scheduler', 'Scheduler', 'Scheduler heartbeat has not run yet', 'No scheduler heartbeat was found in cache.', 'Add the Laravel scheduler cron entry and wait up to five minutes.');
        }

        $lastRunAt = rescue(fn () => Carbon::parse($heartbeat), null, report: false);

        if (! $lastRunAt instanceof Carbon) {
            return $this->warning('scheduler', 'Scheduler', 'Scheduler heartbeat is unreadable', 'The stored scheduler heartbeat could not be parsed.', 'Run php artisan remotematch:heartbeat or wait for scheduler cron.');
        }

        if ($lastRunAt->lt(now()->subMinutes(15))) {
            return $this->warning('scheduler', 'Scheduler', 'Scheduler heartbeat is stale', 'Last heartbeat: '.$lastRunAt->diffForHumans().'.', 'Confirm the cron command is running every minute.');
        }

        return $this->pass('scheduler', 'Scheduler', 'Scheduler heartbeat is recent', 'Last heartbeat: '.$lastRunAt->diffForHumans().'.');
    }

    private function defaultAdminPassword(): array
    {
        try {
            $hasUsersTable = Schema::hasTable('users');
        } catch (Throwable $exception) {
            return $this->fail('default-admin-password', 'Security', 'Unable to inspect admin users', $exception->getMessage(), 'Fix the database connection first.');
        }

        if (! $hasUsersTable) {
            return $this->fail('default-admin-password', 'Security', 'Users table is missing', 'Admin account checks require the users table.', 'Run php artisan migrate.');
        }

        $count = User::query()
            ->where('role', 'admin')
            ->where('must_change_password', true)
            ->count();

        if ($count > 0) {
            return $this->fail('default-admin-password', 'Security', 'Default admin password must be changed', "{$count} admin account(s) still require password rotation.", 'Log in to /admin and set a new password.');
        }

        return $this->pass('default-admin-password', 'Security', 'Admin default password warning is clear', 'No admin accounts require forced password rotation.');
    }

    /**
     * @param  array<int, string>  $keys
     */
    private function thirdPartyCredentials(string $enabledKey, array $keys, string $title, string $action): array
    {
        try {
            $enabled = $this->settings->isEnabled($enabledKey);
        } catch (Throwable $exception) {
            return $this->fail($enabledKey, 'Integrations', "Unable to inspect {$title}", $exception->getMessage(), 'Fix database/cache access for platform settings.');
        }

        if (! $enabled) {
            return $this->warning($enabledKey, 'Integrations', "{$title} is disabled", 'The feature is disabled in settings.', 'Enable it only after valid credentials are configured.');
        }

        try {
            $missing = collect($keys)
                ->filter(fn (string $key): bool => blank($this->settings->get($key)))
                ->values();
        } catch (Throwable $exception) {
            return $this->fail($enabledKey, 'Integrations', "Unable to inspect {$title}", $exception->getMessage(), 'Fix database/cache access for platform settings.');
        }

        if ($missing->isNotEmpty()) {
            return $this->fail($enabledKey, 'Integrations', "{$title} is incomplete", 'Missing: '.$missing->implode(', ').'.', $action);
        }

        return $this->pass($enabledKey, 'Integrations', "{$title} is configured", 'Required settings are present.');
    }

    private function importedJobs(): array
    {
        try {
            $hasImportedJobsTable = Schema::hasTable('imported_jobs');
        } catch (Throwable $exception) {
            return $this->fail('imported-jobs', 'Jobs', 'Unable to inspect imported jobs', $exception->getMessage(), 'Fix the database connection first.');
        }

        if (! $hasImportedJobsTable) {
            return $this->fail('imported-jobs', 'Jobs', 'Imported jobs table is missing', 'The imported_jobs table is required for previews and matching.', 'Run php artisan migrate.');
        }

        $count = ImportedJob::query()->active()->count();

        if ($count === 0) {
            return $this->warning('imported-jobs', 'Jobs', 'No active imported jobs found', 'The homepage will show the empty state until jobs are synced or added.', 'Configure active job sources and run php artisan jobs:sync-sources.');
        }

        return $this->pass('imported-jobs', 'Jobs', 'Active imported jobs are available', "{$count} active job(s) are available for previews and matching.");
    }

    private function maintenanceMode(): array
    {
        try {
            $enabled = $this->settings->isEnabled('general.maintenance_mode_enabled');
        } catch (Throwable $exception) {
            return $this->warning('maintenance-mode', 'Operations', 'Unable to inspect maintenance mode', $exception->getMessage(), 'Fix database/cache access for platform settings.');
        }

        if ($enabled) {
            return $this->warning('maintenance-mode', 'Operations', 'Platform maintenance mode is enabled', 'Public frontend routes are blocked for normal users.', 'Disable maintenance mode before launch.');
        }

        return $this->pass('maintenance-mode', 'Operations', 'Platform maintenance mode is disabled', 'Public frontend routes are available.');
    }

    private function installationLock(): array
    {
        if (! $this->installer->isInstalled()) {
            return $this->fail('installation-lock', 'Security', 'Installation lock is missing', 'The web installer is not locked.', 'Complete the browser installer or run php artisan remotematch:install on a configured installation.');
        }

        $record = $this->installer->installationRecord();
        $domain = (string) ($record['domain'] ?? '');

        if ($domain === '' || blank($record['domain_hash'] ?? null)) {
            return $this->fail('installation-lock', 'Security', 'Installation domain lock is incomplete', 'The installation record is missing the domain or domain hash.', 'Recreate the installation lock from the installed domain.');
        }

        $request = request();

        if ($request instanceof Request && ! $this->installer->isCurrentDomainAllowed($request)) {
            return $this->fail('installation-lock', 'Security', 'Current domain does not match installation lock', 'Installed domain: '.$domain.'. Current domain: '.$request->getHost().'.', 'Use the installed domain or contact the script owner.');
        }

        return $this->pass('installation-lock', 'Security', 'Installation domain lock is active', 'Installed domain: '.$domain.'.');
    }

    private function demoMode(): array
    {
        if ((bool) config('admin.demo_mode')) {
            return $this->warning('demo-mode', 'Operations', 'Admin demo mode is enabled', 'Admin write actions are blocked for public demo safety.', 'Set DEMO_MODE=false before selling or launching a customer site.');
        }

        return $this->pass('demo-mode', 'Operations', 'Admin demo mode is disabled', 'Admin write actions are available to authenticated admins.');
    }

    private function pass(string $key, string $group, string $title, string $details, ?string $action = null): array
    {
        return compact('key', 'group') + [
            'status' => 'pass',
            'title' => $title,
            'details' => $details,
            'action' => $action,
        ];
    }

    private function warning(string $key, string $group, string $title, string $details, ?string $action = null): array
    {
        return compact('key', 'group') + [
            'status' => 'warning',
            'title' => $title,
            'details' => $details,
            'action' => $action,
        ];
    }

    private function fail(string $key, string $group, string $title, string $details, ?string $action = null): array
    {
        return compact('key', 'group') + [
            'status' => 'fail',
            'title' => $title,
            'details' => $details,
            'action' => $action,
        ];
    }
}
