<?php

namespace App\Http\Middleware;

use App\Services\DeploymentService;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpFoundation\Response;

/**
 * Ties cookie security, HTTPS enforcement and host enforcement to the ACTUAL request so
 * a panel-domain change can never lock the operator out nor expose the panel on the wrong
 * host. Runs behind TrustProxies (prod), so isSecure()/getHost() reflect Caddy's
 * X-Forwarded-* headers.
 *
 * - The session cookie's `Secure` flag follows the real request scheme, so the bare-IP /
 *   plain-HTTP recovery path always receives its cookie and can log in.
 * - When a domain is active, ONLY that domain (over HTTPS) and the literal server IP
 *   (over HTTP, the recovery path) may serve the panel. Any other hostname — a stale/old
 *   domain whose certificate Caddy may still hold, or an attacker's domain pointed at this
 *   server — is refused, so the authenticated panel is never served in plaintext or on an
 *   unintended host. Caddy's TLS-ask + the health check are exempt.
 * - In bare-IP mode app.url is kept in step with the request, so generated absolute URLs
 *   never point at a removed/old domain left in .env.
 */
class PanelScheme
{
    public function __construct(private DeploymentService $deployment) {}

    public function handle(Request $request, Closure $next): Response
    {
        // Cookie security follows the real request scheme (never a static .env flag).
        config(['session.secure' => $request->isSecure()]);

        $domain = $this->deployment->domain();

        // Bare-IP mode: no TLS, no host restriction. Keep app.url on the current request
        // so URLs don't point at a domain that was removed from service.
        if ($domain === null) {
            config(['app.url' => $request->getSchemeAndHttpHost()]);

            return $next($request);
        }

        // Internal endpoints must answer on any host/scheme (Caddy's on-demand TLS ask is
        // a plain-HTTP call with an internal Host; the health check + the terminal sidecar's
        // resolve call likewise come over the private network with a service-name Host).
        if ($request->is('_caddy/*', 'up', '_internal/*')) {
            return $next($request);
        }

        $host = strtolower($request->getHost());

        // The literal server IP is the plaintext recovery path — always served over HTTP.
        // Pin app.url to the IP request too, so login/onboarding redirects (absolute
        // route() URLs) stay on the reachable IP instead of bouncing to the (possibly
        // unreachable) domain that AppServiceProvider set as app.url.
        if (filter_var($host, FILTER_VALIDATE_IP) !== false) {
            config(['app.url' => $request->getSchemeAndHttpHost()]);

            return $next($request);
        }

        // Only the active domain may serve the panel by hostname.
        if ($host !== $domain) {
            abort(404);
        }

        // Force HTTPS for the active domain — on the default TLS port (443). Build the URL
        // explicitly so a non-standard HTTP APP_PORT is never carried into the https:// URL
        // (Caddy serves TLS only on 443; https://domain:8080 would be unreachable).
        //
        // In external-proxy mode the upstream terminates TLS and owns HTTP->HTTPS; this Caddy is
        // HTTP-only, so the app must NOT bounce to https://domain:443 (unreachable). The forwarded
        // scheme still drives cookie-Secure/HSTS above.
        if (! $this->deployment->externalTls() && ! $request->isSecure()) {
            return redirect()->to('https://'.$domain.$request->getRequestUri(), 301);
        }

        // Serving the active domain over HTTPS: force generated URLs (assets, routes) to
        // https://<domain> so they MATCH the page scheme. Critical in external-proxy mode, where
        // the upstream terminates TLS and the forwarded scheme may not be trusted here — without
        // this asset() emits http:// URLs that the HTTPS page's CSP ('self') blocks, leaving the
        // panel with no CSS/JS. The bare-IP recovery path returned earlier and stays on HTTP.
        URL::forceRootUrl('https://'.$domain);
        URL::forceScheme('https');

        return $next($request);
    }
}
