<?php

namespace App\Services\Proxy;

use App\Models\ProxyHost;
use App\Support\KnownHostnames;

/**
 * Bringt die Liste auf den Stand der Installation und misst, was jeder Name
 * gerade wirklich ausliefert.
 *
 * Zwei getrennte Hälften, mit Absicht: `sync()` kostet nichts und darf beim
 * Öffnen der Seite laufen, `check()` geht ins Netz und läuft auf Knopfdruck
 * oder nach Zeitplan. Beim Seitenaufruf zu messen hieße, den Betreiber auf ein
 * halbes Dutzend TLS-Handschläge warten zu lassen — und einer davon ist immer
 * der Name, der gerade nicht auflöst.
 */
class CertificateSweep
{
    public function __construct(private CertificateInspector $inspector) {}

    /**
     * Die Namen aus der Umgebung in die Liste übernehmen.
     *
     * @return int wie viele Zeilen neu hinzugekommen sind
     */
    public function sync(): int
    {
        $bekannt = KnownHostnames::all();

        $this->pruneVanished($bekannt);

        $added = 0;

        foreach ($bekannt as $hostname => $purpose) {
            $existing = ProxyHost::query()->where('hostname', $hostname)->first();

            if ($existing === null) {
                ProxyHost::create([
                    'hostname' => $hostname,
                    'purpose' => $purpose,
                    'source' => 'config',
                ]);
                $added++;

                continue;
            }

            // Ein von Hand angelegter Name, der inzwischen auch in der Umgebung
            // steht, bleibt „manual" — sonst verlöre der Betreiber die
            // Möglichkeit, ihn wieder zu entfernen, ohne je etwas dafür getan
            // zu haben.
            if ($existing->isFromConfig() && $existing->purpose !== $purpose) {
                $existing->forceFill(['purpose' => $purpose])->save();
            }
        }

        return $added;
    }

    /**
     * Zeilen, die aus der Umgebung kamen und dort nicht mehr stehen.
     *
     * Zwei Fälle, und sie brauchen zwei Antworten:
     *
     *  - Die Zeile hatte NIE ein Zertifikat. Dann war sie ein Fehlgriff — so
     *    kam `127.0.0.1` aus `ADMIN_HOSTS` in die Liste, wo eine nackte IP als
     *    Wiederzugang stehen darf, für die es aber nie ein Zertifikat geben
     *    wird. Sie geht weg; ein dauerhaft roter Eintrag, den niemand beheben
     *    kann, macht die Übersicht unlesbar.
     *  - Die Zeile HAT ein Zertifikat. Dann wird sie zu einem Eintrag von Hand,
     *    statt zu verschwinden. Das Zertifikat läuft ja weiter, und etwas mit
     *    einer laufenden Frist still aus einer Übersicht zu nehmen ist das
     *    Gegenteil einer Übersicht — der Betreiber entfernt sie selbst.
     *
     * @param  array<string, string>  $bekannt
     */
    private function pruneVanished(array $bekannt): void
    {
        $verschwunden = ProxyHost::query()
            ->where('source', 'config')
            ->whereNotIn('hostname', array_keys($bekannt))
            ->get();

        foreach ($verschwunden as $host) {
            if ($host->hasCertificate()) {
                $host->forceFill(['source' => 'manual'])->save();

                continue;
            }

            $host->delete();
        }
    }

    /** @return array{checked: int, failing: int} */
    public function check(): array
    {
        $checked = 0;
        $failing = 0;

        foreach (ProxyHost::query()->get() as $host) {
            $result = $this->inspector->inspect($host->hostname);

            $host->forceFill([
                'certificate_expires_at' => $result['expires_at'],
                'certificate_checked_at' => now(),
                'certificate_error' => $result['error'],
            ])->save();

            $checked++;
            if ($result['error'] !== null) {
                $failing++;
            }
        }

        return ['checked' => $checked, 'failing' => $failing];
    }
}
