<?php

use Illuminate\Support\Facades\Route;

// Root → NOC dashboard SPA (built into public/noc/). The JSON health
// response that used to live here is still reachable at /health for
// uptime probes. Browsers visiting the bare URL now land on the
// dashboard instead of a confusing JSON blob.
Route::get('/', fn () => redirect('/noc/'));

Route::get('/health', function () {
    return response()->json([
        'app' => config('app.name'),
        'version' => '1.0.0',
        'status' => 'ok',
    ]);
});

// Sanctum CSRF cookie (used by the SPA before login).
Route::get('/sanctum/csrf-cookie', function () {
    return response()->noContent();
});

// Stub `login` named route so route('login') resolves. Sanctum's auth
// middleware redirects unauthenticated *non-JSON* requests here. The
// portal is API-first — there's no HTML login page — so this returns
// the same 401 JSON the middleware would have produced if it had
// recognised the request as JSON in the first place.
Route::get('/login', fn () => response()->json(['message' => 'Unauthenticated.'], 401))
    ->name('login');

/*
|--------------------------------------------------------------------------
| Public identification — /bot.txt
|--------------------------------------------------------------------------
| Hosting servers that we probe will see our User-Agent in their access
| logs. The UA points here so admins can verify we're legitimate, see
| our published source IPs, and decide whether to whitelist us in their
| firewall (CSF/LFD/CXS) and WAF (ModSec/Cloudflare).
|
| The IP list is read from config('monitoring.probe_ips'), settable via
| env: PROBE_IPS=1.2.3.4,5.6.7.8 — defaults to "auto-detect on first
| outbound request" if not set. Operators MUST set this in production.
*/
Route::get('/bot.txt', function () {
    $ips = config('monitoring.probe_ips', []);
    $ipList = empty($ips)
        ? "    (operator has not yet declared static probe IPs — see PROBE_IPS in .env)"
        : implode("\n", array_map(fn ($ip) => "    {$ip}", $ips));

    $appName = config('app.name', 'HostSeba NOC');
    $appUrl = rtrim(config('app.url') ?: 'https://noc.hostseba.com', '/');
    $contact = config('monitoring.contact', 'support@hostseba.com');
    $generated = now()->toIso8601String();

    $body = <<<TXT
# {$appName} — Monitoring identification
#
# This domain runs a monitoring probe. If you see User-Agent
#   "HostSebaNOC/*" in your access or firewall logs, it came from us.
#
# What we do:
#   - HTTP/HTTPS GET requests to URLs the server operator added
#     to their dashboard
#   - TCP connect tests to ports the operator configured
#   - ICMP echo (ping) to IPs the operator configured
#
# What we DO NOT do:
#   - POST/PUT/DELETE requests to your servers
#   - Authentication brute-force
#   - Form submission
#   - Vulnerability scanning
#
# Frequency:
#   Default 60 seconds between checks. We use exponential backoff on
#   failure (60s, 2m, 4m, ... up to 1h) and stop entirely if we
#   detect we're being blocked.
#
# Source IPs:
{$ipList}
#
# To stop probes from this monitor:
#   1. Sign in at {$appUrl}
#   2. Servers → click the server → Agent → "Remove from monitoring"
#
# To allow probes through your firewall:
#   - CSF: csf -a <ip> "HostSeba NOC monitor"
#   - Cloudflare: create rule allowing the IPs listed above
#
# Contact: {$contact}
# Generated: {$generated}

TXT;

    return response($body, 200, [
        'Content-Type' => 'text/plain; charset=utf-8',
        'Cache-Control' => 'public, max-age=3600',
    ]);
});

/*
|--------------------------------------------------------------------------
| Agent installer & runtime downloads
|--------------------------------------------------------------------------
| The installer commands shown to admins point at these URLs. We look for
| files in two places, in order:
|   1) storage/app/agent/{filename}   — operator-managed override
|   2) resources/agent/{filename}     — files bundled with this release
|
| This means the install URLs work the moment the dashboard is deployed,
| with no extra "copy files into storage" step. To override (e.g. patch
| the installer for one server), drop the file into storage/app/agent and
| it wins.
|
| For install.sh / install.ps1 we ALSO rewrite the hardcoded backend URL
| to whatever URL the operator is currently hitting. This means:
|     curl https://my-real-domain.com/install.sh | bash -s -- --token X
| ...works without an explicit --backend argument, because the install.sh
| served from my-real-domain.com has its BACKEND_URL line replaced before
| transmission. The trait-based controller logic generates commands with
| --backend already, but this is a safety net for hand-typed commands.
*/
$agentFile = function (string $filename, string $contentType) {
    $candidates = [
        storage_path("app/agent/{$filename}"),
        resource_path("agent/{$filename}"),
    ];
    foreach ($candidates as $path) {
        if (file_exists($path)) {
            return response()->file($path, [
                'Content-Type' => $contentType,
                'Cache-Control' => 'public, max-age=300',
            ]);
        }
    }
    abort(404, "Agent file '{$filename}' not found. Drop it into storage/app/agent/ or resources/agent/.");
};

// Variant that returns the file with the BACKEND_URL default rewritten to
// the currently-incoming hostname. Use only for installer scripts.
$installerFile = function (string $filename, string $contentType, \Illuminate\Http\Request $request) use ($agentFile) {
    $candidates = [
        storage_path("app/agent/{$filename}"),
        resource_path("agent/{$filename}"),
    ];
    $found = null;
    foreach ($candidates as $path) {
        if (file_exists($path)) { $found = $path; break; }
    }
    abort_unless($found, 404, "Installer '{$filename}' not found.");

    // Derive the URL the way DerivesInstallBaseUrl does. Keep this in sync
    // with App\Http\Concerns\DerivesInstallBaseUrl::installBaseUrl() — same
    // priority order: explicit APP_URL wins, then incoming request host.
    $configured = rtrim((string) config('app.url'), '/');
    // Treat any localhost-pointed APP_URL as "defaulty" for installer
    // rewriting. If a real agent on a remote server fetches install.sh,
    // baking in localhost is always wrong — use the actual hostname
    // they reached us at (ngrok / production domain / load balancer).
    $isDefaulty = $configured === ''
        || preg_match('#^https?://localhost(:\d+)?$#i', $configured)
        || preg_match('#^https?://127\.0\.0\.1(:\d+)?$#i', $configured);
    if ($isDefaulty) {
        $scheme = $request->getScheme() ?: 'http';
        $host = $request->getHost();
        $port = $request->getPort();
        $standardPort = ($scheme === 'https' && $port == 443) || ($scheme === 'http' && $port == 80);
        $base = $standardPort ? "{$scheme}://{$host}" : "{$scheme}://{$host}:{$port}";
    } else {
        $base = $configured;
    }

    $contents = file_get_contents($found);

    // Rewrite the bash default. Pattern matches both single-line forms.
    if (str_ends_with($filename, '.sh')) {
        $contents = preg_replace(
            '/^BACKEND_URL="\$\{BACKEND_URL:-[^}]+\}"$/m',
            'BACKEND_URL="${BACKEND_URL:-' . $base . '}"',
            $contents
        );
    }
    // Rewrite the PowerShell default.
    if (str_ends_with($filename, '.ps1')) {
        $contents = preg_replace(
            '/^\$Backend\s*=\s*"[^"]*"\s*$/m',
            '$Backend = "' . $base . '"',
            $contents
        );
    }

    return response($contents, 200, [
        'Content-Type' => $contentType,
        'Cache-Control' => 'no-store',  // backend URL changes per-host, never cache
    ]);
};

Route::get('/install.sh',         fn (\Illuminate\Http\Request $r) => $installerFile('install.sh',      'text/x-shellscript', $r));
Route::get('/install.ps1',        fn (\Illuminate\Http\Request $r) => $installerFile('install.ps1',     'text/plain', $r));
Route::get('/install-macos.sh',   fn (\Illuminate\Http\Request $r) => $installerFile('install-macos.sh','text/x-shellscript', $r));
Route::get('/agent/agent.sh',     fn () => $agentFile('agent.sh',        'text/x-shellscript'));
Route::get('/agent/agent-hosting.sh', fn () => $agentFile('agent-hosting.sh', 'text/x-shellscript'));
Route::get('/agent/agent-security.sh', fn () => $agentFile('agent-security.sh', 'text/x-shellscript'));
Route::get('/agent/hostseba-noc-hosting.cron', fn () => $agentFile('hostseba-noc-hosting.cron', 'text/plain'));
Route::get('/agent/agent.ps1',    fn () => $agentFile('agent.ps1',       'text/plain'));
Route::get('/agent/uninstall.sh', fn () => $agentFile('uninstall.sh',    'text/x-shellscript'));
Route::get('/agent/uninstall.ps1',fn () => $agentFile('uninstall.ps1',   'text/plain'));
Route::get('/agent/whitelist.sh', fn () => $agentFile('whitelist.sh',    'text/x-shellscript'));

// Probe location node installer + runtime. Same load-from-resources pattern
// as the agent. install.sh has BACKEND_URL rewritten to the requesting host.
$probeFile = function (string $filename, string $contentType) {
    $candidates = [
        storage_path("app/probe/{$filename}"),
        resource_path("probe/{$filename}"),
    ];
    foreach ($candidates as $path) {
        if (file_exists($path)) {
            return response()->file($path, [
                'Content-Type' => $contentType,
                'Cache-Control' => 'public, max-age=300',
            ]);
        }
    }
    abort(404, "Probe file '{$filename}' not found.");
};

Route::get('/probe/install.sh', function (\Illuminate\Http\Request $r) {
    $candidates = [storage_path('app/probe/install.sh'), resource_path('probe/install.sh')];
    $found = null;
    foreach ($candidates as $p) { if (file_exists($p)) { $found = $p; break; } }
    abort_unless($found, 404);

    // Same URL-rewrite logic as the agent installer — see DerivesInstallBaseUrl trait
    $configured = rtrim((string) config('app.url'), '/');
    // Treat any localhost-pointed APP_URL as "defaulty" for installer
    // rewriting. If a real agent on a remote server fetches install.sh,
    // baking in localhost is always wrong — use the actual hostname
    // they reached us at (ngrok / production domain / load balancer).
    $isDefaulty = $configured === ''
        || preg_match('#^https?://localhost(:\d+)?$#i', $configured)
        || preg_match('#^https?://127\.0\.0\.1(:\d+)?$#i', $configured);
    if ($isDefaulty) {
        $scheme = $r->getScheme() ?: 'http';
        $host = $r->getHost();
        $port = $r->getPort();
        $standard = ($scheme === 'https' && $port == 443) || ($scheme === 'http' && $port == 80);
        $base = $standard ? "{$scheme}://{$host}" : "{$scheme}://{$host}:{$port}";
    } else {
        $base = $configured;
    }

    $contents = file_get_contents($found);
    $contents = preg_replace(
        '/^BACKEND_URL="\$\{BACKEND_URL:-[^}]+\}"$/m',
        'BACKEND_URL="${BACKEND_URL:-' . $base . '}"',
        $contents
    );
    return response($contents, 200, [
        'Content-Type' => 'text/x-shellscript',
        'Cache-Control' => 'no-store',
    ]);
});
Route::get('/probe/probe.sh', fn () => $probeFile('probe.sh', 'text/x-shellscript'));
Route::get('/probe/uninstall.sh', fn () => $probeFile('uninstall.sh', 'text/x-shellscript'));
