<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class HealthController extends Controller
{
    /**
     * Schema facts this deploy cannot run without.
     *
     * The entrypoint now fails the boot on a migration error, which is the
     * primary defence. This is the second one, and it answers a different
     * question: not "did migrate exit non-zero" but "is the schema this code
     * was written against actually here". A container that booted from a
     * half-applied migration, or a database restored from an older dump, looks
     * perfectly healthy until a member hits the screen that needs the column.
     *
     * Each entry is a column some code path dereferences without checking. The
     * difference between this and no check is finding out in minutes rather
     * than hearing it from a client in a month.
     *
     * @var array<string, array<int, string>>
     */
    private const REQUIRED_SCHEMA = [
        'academies' => ['branding_version', 'address'],
        'invoices' => ['branch_id'],
        'invoice_items' => ['academy_id'],
        'transactions' => ['branch_id'],
        'installments' => ['academy_id'],
        'participants' => ['checkin_key_version'],
        'users' => ['email_is_synthetic'],
        'device_tokens' => ['user_agent'],
        'service_requests' => ['training_session_id', 'attachment_path'],
        'website_news' => ['channel'],
    ];

    /** @var array<int, string> */
    private const REQUIRED_TABLES = [
        'portal_invitations',
        'payment_proofs',
        'checkin_consumptions',
        'invoice_number_counters',
        'consents',
        'account_deletion_requests',
    ];
    public function __invoke(): JsonResponse
    {
        $checks = [];

        try {
            DB::connection()->getPdo();
            $checks['database'] = 'ok';
        } catch (\Throwable) {
            $checks['database'] = 'fail';
        }

        try {
            Cache::put('health_check', true, 5);
            $checks['cache'] = Cache::get('health_check') ? 'ok' : 'fail';
        } catch (\Throwable) {
            $checks['cache'] = 'fail';
        }

        $checks['schema'] = $this->schemaCheck($missing);

        if ($missing !== []) {
            // Named, because "schema: fail" sends someone reading migrations
            // for an hour and "missing invoices.branch_id" does not.
            $checks['schema_missing'] = $missing;
        }

        $checks['php_version'] = PHP_VERSION;
        $checks['laravel_version'] = app()->version();
        $checks['timestamp'] = now()->toIso8601String();

        $allOk = ! in_array('fail', $checks, true);

        return response()->json([
            'status' => $allOk ? 'healthy' : 'degraded',
            'checks' => $checks,
        ], $allOk ? 200 : 503);
    }

    /**
     * @param  array<int, string>  $missing  filled with what is absent
     */
    private function schemaCheck(?array &$missing = null): string
    {
        $missing = [];

        try {
            foreach (self::REQUIRED_TABLES as $table) {
                if (! Schema::hasTable($table)) {
                    $missing[] = $table;
                }
            }

            foreach (self::REQUIRED_SCHEMA as $table => $columns) {
                if (! Schema::hasTable($table)) {
                    $missing[] = $table;
                    continue;
                }

                foreach ($columns as $column) {
                    if (! Schema::hasColumn($table, $column)) {
                        $missing[] = "{$table}.{$column}";
                    }
                }
            }
        } catch (\Throwable) {
            return 'fail';
        }

        return $missing === [] ? 'ok' : 'fail';
    }
}
