<?php

namespace App\Services;

use App\Models\LocationCheckResult;
use App\Models\ProbeLocation;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
 * Records check results for the "primary NOC probe" — i.e. checks performed
 * by THIS server (the NOC itself), not by remote probe nodes.
 *
 * Why this exists:
 * - Without this, only remote probes would feed location_check_results, and
 *   a single-NOC deployment (no remote probes yet) would show 0% uptime
 *   forever because nothing writes to that table.
 * - Equally important: the dashboard's "Probe Locations" page shows each
 *   probe's last_seen_at + health status. The remote-probe middleware
 *   (AuthenticateProbe) updates last_seen_at on every push. We need the
 *   same heartbeat behavior for noc-primary, otherwise it would always
 *   render as "stale / never seen" even though it's the most active probe.
 *
 * Used by:
 * - ManualPingService (every server probe)
 * - WebsiteCheckerService (every website check)
 * - Anywhere else we want to record a check result attributed to the NOC.
 */
class PrimaryProbeRecorder
{
    /** Cache key for primary probe ID lookup. The id never changes once seeded. */
    protected const PROBE_ID_CACHE_KEY = 'probe:noc-primary:id';

    /** Cache key for last heartbeat write. Throttled so we don't update_at
     *  on every check — once every 30s is enough to keep the dashboard
     *  showing "healthy". */
    protected const HEARTBEAT_CACHE_KEY = 'probe:noc-primary:last-heartbeat';

    /** How often to actually persist the heartbeat (seconds). */
    protected const HEARTBEAT_INTERVAL = 30;

    /**
     * Record a check result attributed to noc-primary AND keep its heartbeat
     * fresh.
     *
     * Returns true on success, false if the noc-primary location doesn't
     * exist (e.g. migration not yet run). Errors are swallowed and logged
     * — failure to record a location check should never break the actual
     * check that triggered it.
     */
    public function record(Model $target, bool $isUp, ?int $statusCode = null, ?int $responseMs = null, ?string $error = null): bool
    {
        $probeId = $this->primaryProbeId();
        if (!$probeId) return false;

        try {
            LocationCheckResult::create([
                'probe_location_id' => $probeId,
                'target_type' => $target->getMorphClass(),
                'target_id' => $target->getKey(),
                'checked_at' => now(),
                'is_up' => $isUp,
                'status_code' => $statusCode,
                'response_time_ms' => $responseMs,
                'error' => $error ? mb_substr($error, 0, 250) : null,
            ]);

            $this->touchHeartbeat();
            return true;
        } catch (\Throwable $e) {
            Log::warning('PrimaryProbeRecorder failed: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Look up + cache the noc-primary probe row's ID. The row is seeded by
     * migration 000022 and we don't allow it to be deleted, so this is a
     * forever-cached value once known. Cache miss = first read after deploy
     * or after a `cache:clear`.
     */
    protected function primaryProbeId(): ?int
    {
        return Cache::rememberForever(self::PROBE_ID_CACHE_KEY, function () {
            return ProbeLocation::where('code', 'noc-primary')->value('id');
        });
    }

    /**
     * Update last_seen_at on the noc-primary row. Throttled — most NOC
     * deployments check 50+ targets per minute, so writing on each one
     * would be wasteful. Once every 30s is plenty for the dashboard.
     */
    protected function touchHeartbeat(): void
    {
        $now = now();
        $last = Cache::get(self::HEARTBEAT_CACHE_KEY);

        if ($last && $now->diffInSeconds($last) < self::HEARTBEAT_INTERVAL) {
            return;  // throttle: skip write
        }

        $probeId = $this->primaryProbeId();
        if (!$probeId) return;

        ProbeLocation::where('id', $probeId)->update([
            'last_seen_at' => $now,
            'agent_version' => 'builtin-' . config('app.version', '1.0'),
            'public_ip' => request()?->server('SERVER_ADDR') ?: null,
        ]);

        Cache::put(self::HEARTBEAT_CACHE_KEY, $now, now()->addMinutes(5));
    }
}
