<?php

declare(strict_types=1);

namespace App\Support;

use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * Tells apart the two ways frontend-ui reaches this API.
 *
 * A hard page load is rendered by the Nuxt server, which calls us server to server over an internal
 * hostname. Anything the visitor's browser triggers afterwards (typing a search, changing a filter)
 * comes straight from the browser over the public hostname. Only the browser leg represents a real
 * person doing something, and only the browser leg carries anything we can attribute to a visitor.
 */
final class RequestSource
{
    /**
     * Was this request made by the Nuxt server rather than a visitor's browser?
     */
    public static function isServerSide(Request $request): bool
    {
        if (self::matchesServerSideHost($request->getHost())) {
            return true;
        }

        // Fallback for environments where the internal hostname has not been configured yet.
        // Server side fetches run outside a browser, so they send neither of these; every browser
        // request to this API is cross-origin and therefore always carries an Origin.
        return !$request->headers->has('Origin') && !$request->headers->has('Referer');
    }

    /**
     * Opaque per-visitor key, for grouping repeat requests without storing anything identifying.
     *
     * Only meaningful for browser requests - see isServerSide().
     */
    public static function visitorFingerprint(Request $request): string
    {
        return hash('sha256', implode('|', [
            (string) (tenant('id') ?? ''),
            self::clientIp($request),
            (string) $request->userAgent(),
        ]));
    }

    private static function matchesServerSideHost(string $host): bool
    {
        $host = Str::lower($host);
        $patterns = array_filter(array_map(
            fn ($pattern) => Str::lower(trim((string) $pattern)),
            (array) config('app.server_side_render_hosts', [])
        ));

        return $patterns !== [] && Str::is($patterns, $host);
    }

    /**
     * The visitor's address as best we can tell.
     *
     * Trusted proxies are deliberately not configured application wide, so the forwarded header is
     * read here directly. It only ever feeds a hash used for grouping, never an access decision, so
     * a spoofed value can do no more than skew the sender's own grouping.
     */
    private static function clientIp(Request $request): string
    {
        $forwarded = $request->headers->get('X-Forwarded-For');

        if ($forwarded) {
            $first = trim(Str::before($forwarded, ','));
            if ($first !== '') {
                return $first;
            }
        }

        return (string) $request->ip();
    }
}
