<?php

namespace Instinctor\Core;

/**
 * Pro subdomain router (2026-07-22). Handles requests on <slug>.instinctor.com:
 *
 *   - Pro owner   -> rewrite REQUEST_URI to /<slug>/ so the existing /:slug
 *                    catch-all + impulse-page renderer serve their site here.
 *   - Free owner  -> 301 to instinctor.com/<slug> (path stays the free URL).
 *   - unknown     -> 302 to the apex.
 *   - reserved    -> untouched (served like the main app).
 *
 * The reverse leg (Pro instinctor.com/<slug> -> subdomain 301) lives in the
 * /:slug catch-all, host-guarded to the main domain, so there is no loop.
 *
 * Auth stays on the main domain: /admin, /dashboard, /sso, /api, etc. on a
 * subdomain 301 back to instinctor.com (the auth cookie is host-scoped).
 *
 * FAIL-OPEN: any error -> return, so a bug here can never take down the site.
 * Runs first in StandaloneDriver::dispatch(); returns instantly for the apex.
 */
final class HostRouter
{
    private const BASE = 'instinctor.com';

    /** Subdomains that are NOT tenants: their own nginx blocks, app namespaces, or mail. */
    private const RESERVED = [
        'www', 'admin', 'api', 'assets', 'd', 'depth-maps', 'designers', 'directory', 'help', 'hire',
        'mail', 'partners', 'pricing', 'r', 'reset-password', 'sso', 'start', 'status',
        'subscribe', 'support', 'uploads', 'instinctor',
        'platform', 'demo', 'preview',
        'installer', 'instructor', 'ingine', 'instudio', 'instate', 'instaurant', 'salon',
    ];

    /** Paths that must live on the main domain (host-scoped auth cookie). */
    private const MAIN_ONLY = '#^/(admin|dashboard|sso|editor|login|logout|api|wp-json|wp-admin|admin-ajax)(?:/|$)#i';

    public static function handle(): void
    {
        try {
            self::route();
        } catch (\Throwable $e) {
            // fail open: never break the request pipeline
        }
    }

    private static function route(): void
    {
        $host = strtolower((string) ($_SERVER['HTTP_HOST'] ?? ''));
        $host = (string) preg_replace('/:\d+$/', '', $host);          // strip port
        if ($host === '' || $host === self::BASE) return;             // apex -> normal

        // This install's OWN host is never a tenant subdomain. Once a Pro upgrade
        // provisions a standalone AT <slug>.instinctor.com, that install runs this
        // same code: it saw its own host, looked for a tenant, found a page with
        // owner_id NULL (single-tenant installs have no owner column value) and
        // 302'd its visitors to the apex - the owner's brand-new site served the
        // vendor homepage instead (found provisioning velvet-and-vine, 2026-07-25).
        // Tenant routing belongs to the shared host alone, and APP_URL is what
        // tells each install which one it is.
        if (defined('INSTINCTOR_STANDALONE_URL')) {
            $self = strtolower((string) parse_url((string) INSTINCTOR_STANDALONE_URL, PHP_URL_HOST));
            if ($self !== '' && $self !== self::BASE && $self === $host) return;
        }

        // exactly one label under instinctor.com (not www.x.y, not a custom domain)
        if (!preg_match('/^([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.' . preg_quote(self::BASE, '/') . '$/', $host, $m)) {
            return;
        }
        // ACME / well-known must pass through untouched (Let's Encrypt cert issuance).
        if (strpos((string) ($_SERVER['REQUEST_URI'] ?? ''), '/.well-known/') !== false) return;

        $slug = $m[1];
        if (in_array($slug, self::RESERVED, true)) return;            // reserved subdomain -> normal

        if (!class_exists('\\Instinctor\\Services\\PageService')) return;
        $page      = \Instinctor\Services\PageService::getRoutableBySlug($slug);
        $ownerId   = ($page && !empty($page['owner_id'])) ? (int) $page['owner_id'] : 0;
        $published = $page && (($page['status'] ?? '') === 'published');
        if ($ownerId <= 0 || !$published) {
            self::redirect('https://' . self::BASE . '/', 302);       // unknown subdomain -> apex
        }

        $reqUri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
        $path   = (string) (parse_url($reqUri, PHP_URL_PATH) ?: '/');
        $qs     = (string) (parse_url($reqUri, PHP_URL_QUERY) ?: '');
        $qsPart = $qs !== '' ? '?' . $qs : '';

        // admin/dashboard/sso/etc. -> main domain (cookie scope)
        if (preg_match(self::MAIN_ONLY, $path)) {
            self::redirect('https://' . self::BASE . $reqUri, 301);
        }

        $isPro = class_exists('\\Instinctor\\Core\\TenantPlan')
            && \Instinctor\Core\TenantPlan::isPro($ownerId);

        if ($isPro) {
            // Serve here: prepend /<slug>, keep a trailing slash so the catch-all's
            // trailing-slash 301 does not fire and bounce us off the subdomain.
            $sub = ($path === '' || $path === '/')
                ? '/' . $slug . '/'
                : '/' . $slug . '/' . trim($path, '/') . '/';
            $_SERVER['REQUEST_URI'] = $sub . $qsPart;
            $_SERVER['PATH_INFO']   = $sub;
            $GLOBALS['impulse_on_tenant_subdomain'] = $slug;          // marker for canonical/reverse-guard
            return;                                                   // continue normal dispatch
        }

        // Free owner: the path is their canonical URL.
        self::redirect(
            'https://' . self::BASE . '/' . $slug . ($path === '/' ? '' : $path) . $qsPart,
            301
        );
    }

    private static function redirect(string $url, int $code): void
    {
        header('Location: ' . $url, true, $code);
        exit;
    }
}
