<?php

namespace App\Http\Controllers\Health;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Throwable;

class HealthcheckController extends Controller
{
    private const HEALTHCHECK_TABLE = 'admins';
    private const PUBLIC_DISK = 'public';
    private const PUBLIC_STORAGE_LINK = 'storage';
    private const SECRET_CONFIG_KEY = 'services.healthcheck.secret';
    private const SECRET_QUERY_KEY = 'secret';
    private const CACHE_PROBE_PREFIX = 'healthcheck:probe:';
    private const STATUS_OK = 'OK';
    private const STATUS_FAIL = 'X';

    /**
     * Endpoint público para monitoreo externo. Por defecto sólo expone el
     * status code; el detalle de checks queda disponible con un secreto de
     * diagnóstico para no revelar información operativa al público general.
     */
    public function __invoke(Request $request): Response
    {
        $diagnostics = [
            'db' => $this->runDatabaseCheck(),
            'cache' => $this->runCacheCheck(),
            'storage' => $this->runStorageCheck(),
            'storage_link' => $this->runStorageLinkCheck(),
        ];

        $checks = collect($diagnostics)
            ->map(fn (array $diagnostic) => $diagnostic['status'])
            ->all();

        $status = in_array(self::STATUS_FAIL, $checks, true) ? 503 : 200;
        $message = $this->shouldExposeDiagnostics($request)
            ? $this->formatDiagnostics($diagnostics)
            : '';

        return $this->plainTextResponse($message, $status);
    }

    /**
     * Valida una lectura mínima sobre una tabla real. El detalle técnico queda
     * reservado al modo diagnóstico activado con secreto.
     *
     * @return array{status: string, error: string|null}
     */
    private function runDatabaseCheck(): array
    {
        try {
            DB::table(self::HEALTHCHECK_TABLE)
                ->select('id')
                ->limit(1)
                ->exists();
        } catch (Throwable $exception) {
            report($exception);

            return [
                'status' => self::STATUS_FAIL,
                'error' => class_basename($exception),
            ];
        }

        return [
            'status' => self::STATUS_OK,
            'error' => null,
        ];
    }

    /**
     * Verifica que el link público esperado por Laravel exista y apunte al
     * target configurado. Esto detecta despliegues donde `storage:link` no se
     * ejecutó o quedó apuntando a una ruta incorrecta.
     *
     * @return array{status: string, error: string|null}
     */
    private function runStorageLinkCheck(): array
    {
        $links = (array) config('filesystems.links', []);
        $linkPath = public_path(self::PUBLIC_STORAGE_LINK);
        $targetPath = $links[$linkPath] ?? null;

        if (!is_string($targetPath) || $targetPath === '') {
            return [
                'status' => self::STATUS_FAIL,
                'error' => 'LinkConfigMissing',
            ];
        }

        if (!is_link($linkPath)) {
            return [
                'status' => self::STATUS_FAIL,
                'error' => 'LinkMissing',
            ];
        }

        $resolvedTarget = readlink($linkPath);

        if ($resolvedTarget === false || realpath($linkPath) !== realpath($targetPath)) {
            return [
                'status' => self::STATUS_FAIL,
                'error' => 'LinkInvalid',
            ];
        }

        return [
            'status' => self::STATUS_OK,
            'error' => null,
        ];
    }

    /**
     * Comprueba round-trip de cache con una key efímera para verificar lectura
     * y escritura del backend configurado sin dejar residuos funcionales.
     *
     * @return array{status: string, error: string|null}
     */
    private function runCacheCheck(): array
    {
        $key = self::CACHE_PROBE_PREFIX . Str::random(12);
        $value = Str::random(12);

        try {
            Cache::put($key, $value, 30);
            $cached = Cache::get($key);
            Cache::forget($key);
        } catch (Throwable $exception) {
            report($exception);

            return [
                'status' => self::STATUS_FAIL,
                'error' => class_basename($exception),
            ];
        }

        if ($cached !== $value) {
            return [
                'status' => self::STATUS_FAIL,
                'error' => 'CacheMismatch',
            ];
        }

        return [
            'status' => self::STATUS_OK,
            'error' => null,
        ];
    }

    /**
     * Verifica que el disk público esté resolviendo una ruta escribible. Es una
     * señal barata de que uploads y thumbnails podrán persistirse correctamente.
     *
     * @return array{status: string, error: string|null}
     */
    private function runStorageCheck(): array
    {
        try {
            $root = Storage::disk(self::PUBLIC_DISK)->path('');
        } catch (Throwable $exception) {
            report($exception);

            return [
                'status' => self::STATUS_FAIL,
                'error' => class_basename($exception),
            ];
        }

        $isAvailable = is_dir($root) && is_writable($root);

        if (!$isAvailable) {
            return [
                'status' => self::STATUS_FAIL,
                'error' => 'StorageUnavailable',
            ];
        }

        return [
            'status' => self::STATUS_OK,
            'error' => null,
        ];
    }

    /**
     * @param array<string, array{status: string, error: string|null}> $diagnostics
     */
    private function formatDiagnostics(array $diagnostics): string
    {
        return collect($diagnostics)
            ->map(function (array $diagnostic, string $name): string {
                $line = "{$name}: {$diagnostic['status']}";

                if ($diagnostic['status'] === self::STATUS_FAIL && $diagnostic['error']) {
                    return "{$line} ({$diagnostic['error']})";
                }

                return $line;
            })
            ->implode("\n");
    }

    /**
     * El secreto se compara en forma exacta y, si falta o no coincide, el
     * endpoint se comporta como un monitor público normal.
     */
    private function shouldExposeDiagnostics(Request $request): bool
    {
        $secret = (string) config(self::SECRET_CONFIG_KEY, '');

        if ($secret === '') {
            return false;
        }

        return hash_equals($secret, (string) $request->query(self::SECRET_QUERY_KEY, ''));
    }

    /**
     * Respuesta textual mínima para que el monitoreo use status code y, si hace
     * falta, un resumen legible sin acoplarse a datos sensibles o internos.
     */
    private function plainTextResponse(string $message, int $status = 200): Response
    {
        return response($message, $status)
            ->header('Content-Type', 'text/plain')
            ->header('Cache-Control', 'no-store, no-cache, must-revalidate');
    }
}
