<?php

namespace App\Support;

/**
 * Which addresses genuinely belong to this installation.
 *
 * Derived, never typed. A hard-coded list is the same mistake the price sheet
 * made when it promised "Adresse auf clupilot.cloud" while provisioning issued
 * something else — except here the consequence is a customer being told that a
 * phishing domain is one of ours, or that one of ours is not.
 *
 * Two registrable domains by design: the company's, which serves the website,
 * the portal and the console, and the instance zone, which serves customer
 * workloads. Keeping them apart is the point — a Nextcloud is third-party
 * software that strangers sign in to, and on the same registrable domain as
 * the portal it would share cookie scope with it.
 */
class OfficialDomains
{
    /**
     * The hostnames a customer legitimately sees, in the order they meet them.
     *
     * @return array<int, array{host: string, what: string}>
     */
    public static function hosts(): array
    {
        $entries = [];

        foreach ((array) config('admin_access.site_hosts', []) as $host) {
            $entries[] = ['host' => (string) $host, 'what' => 'site'];
        }

        // The host this page is being served from, always.
        //
        // Without it an installation that has not configured SITE_HOST — a
        // development box, or one reached by a name nobody wrote into the
        // config — listed ONLY the instance zone, and described it as the
        // customers' domain. So the page a visitor opened to check our
        // addresses omitted the address they had just typed, and named the
        // wrong thing as ours. Reported exactly that way.
        //
        // Deduplicated below by hostname, so a configured site host does not
        // appear twice.
        if (($current = request()?->getHost()) && ! in_array($current, array_column($entries, 'host'), true)) {
            $entries[] = ['host' => $current, 'what' => 'site'];
        }

        if ($app = (string) config('admin_access.app_host')) {
            $entries[] = ['host' => $app, 'what' => 'portal'];
        }

        if ($status = (string) config('admin_access.status_host')) {
            $entries[] = ['host' => $status, 'what' => 'status'];
        }

        // The instance zone as a pattern, because the label is per customer and
        // naming one customer's address on a public page names that customer.
        if ($zone = ProvisioningSettings::dnsZone()) {
            $entries[] = ['host' => '*.'.$zone, 'what' => 'instance'];
        }

        // The console is deliberately absent. It is not an address a customer
        // has any business visiting, and printing it on a public page tells an
        // attacker where the operator interface lives.
        return array_values(array_filter($entries, fn (array $e) => $e['host'] !== ''));
    }

    /**
     * The registrable domains behind those hosts — what somebody actually has
     * to check in the address bar.
     *
     * "Everything before the first slash ends in one of these" is a rule a
     * person can apply under time pressure; a list of eight hostnames is not.
     *
     * Keyed by domain, valued by what it is for — so the page never has to
     * work that out from the order it happens to come back in.
     *
     * @return array<string, string>
     */
    public static function registrable(): array
    {
        $domains = [];

        foreach (self::hosts() as $entry) {
            $labels = explode('.', ltrim($entry['host'], '*.'));

            // Last two labels. Not a public-suffix implementation: this runs
            // against an installation's own configured domains, which are known
            // quantities, and pulling in a suffix list to be right about
            // co.uk on a page that says "check the address bar" is not the
            // trade this needs.
            if (count($labels) < 2) {
                continue;
            }

            $domain = implode('.', array_slice($labels, -2));

            // First writer wins: the site is listed before the instance zone,
            // so a single-domain installation is described as the company's
            // rather than as the customers'.
            $domains[$domain] ??= $entry['what'] === 'instance' ? 'instances' : 'company';
        }

        return $domains;
    }
}
