<?php

declare(strict_types=1);

namespace ReleaseInsights;

class Request
{
    public string $request = '/';
    public string $path = '/';
    public ?string $query = null;
    public bool $invalid_slashes = true;

    public function __construct(string $path)
    {
        $request = parse_url($path);

        // Paths that start with multiple slashes don't have a correct (or any) 'path' field via parse_url()
        if (str_starts_with($path, '//')) {
            $this->invalid_slashes = true;
        }

        // Real files are not processes as paths to route
        if ($request !== false) {
            // We sometimes use a fake query on a static asset to force the browser to refresh the cache,
            // we take the query out when checking if the file path exists
            if (file_exists($_SERVER['DOCUMENT_ROOT'] . explode('?', $path)[0])) {
                $this->invalid_slashes = false;
                $this->path = explode('?', $path)[0];
            } else {
                // We have a real path to route and clean up before usage
                $this->request = $path;

                if (isset($request['path'])) {
                    $this->path = $this->cleanPath($request['path']);
                }

                if (isset($request['query'])) {
                    $this->query = $request['query'];
                }

                if (isset($request['path'])) {
                    if (str_ends_with($request['path'], '//')) {
                        // Multiple slashes at the end of the path
                        $this->invalid_slashes = true;
                    } elseif (! str_ends_with($request['path'], '/')) {
                        // Missing slash at the end of the path
                        $this->invalid_slashes = true;
                    } else {
                        $this->invalid_slashes = false;
                    }
                }
            }
        }
    }

    /**
     * Load the controller file
     * @codeCoverageIgnore
     */
    public function loadController(): void
    {
        include CONTROLLERS . $this->getController() . '.php';
    }

    /**
     * Return the name of the controller file for the requested URL
     * If the path is unknown, we send a 404 response
     */
    public function getController(): string
    {
        return match ($this->path) {
            '/'                              => 'homepage',
            '/about/'                        => 'about',
            '/beta/'                         => 'beta',
            '/nightly/'                      => 'nightly',
            '/release/'                      => 'release',
            '/api/beta/crashes/'             => 'api/beta_crashes',
            '/api/firefox/chemspills/'       => 'api/chemspill_releases',
            '/api/external/'                 => 'api/external',
            '/api/nightly/'                  => 'api/nightly',
            '/api/nightly/crashes/'          => 'api/nightly_crashes',
            '/api/release/schedule/'         => 'api/release_schedule',
            '/api/esr/releases/'             => 'api/esr_releases',
            '/api/firefox/releases/'         => 'api/firefox_releases',
            '/api/firefox/releases/esr/'     => 'api/esr_release_pairs',
            '/api/firefox/releases/future/'  => 'api/firefox_releases_future',
            '/api/firefox/calendar/future/'  => 'api/future_calendar',
            '/api/release/owners/'           => 'api/release_owners',
            '/api/wellness/days/'            => 'api/wellness_days',
            '/calendar/'                     => 'calendar',
            '/calendar/monthly/'             => 'calendar_monthly',
            '/calendar/release/schedule/'    => 'ics_release_schedule',
            '/release/owners/'               => 'release_owners',
            '/rss/'                          => 'rss',
            '/sitemap/'                      => 'sitemap',
            default                          => '404',
        };
    }

    /**
     * Normalize path before comparing the string to a list of valid paths
     */
    public function cleanPath(string $path): string
    {
        if ($path == '/' || $path == '//') {
            return '/';
        }

        $path = explode('/', $path);
        $path = array_filter($path); // Remove empty items
        $path = array_values($path); // Reorder keys

        return '/' . implode('/', $path) . '/';
    }

    /**
     * Display a static page.
     * Send all the necessary headers to bypass http server caching
     * @codeCoverageIgnore
     */
    public static function waitingPage(string $action): void
    {
        if ($action == 'load') {
            // This is a long-running process when we fetch and generate data
            set_time_limit(0);
            // We need to set the encoding or the browser will wait to parse the content
            header('Content-type: text/html; charset=utf-8');
            // Explicitly disable caching so Varnish and other upstreams won't cache.
            header("Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0");
            // Emulate the header BigPipe sends so we can test through Varnish.
            header('Surrogate-Control: BigPipe/1.0');
            // Disable gzip compression to allow sending a chunk of html
            header('Content-Encoding: none');
            // Setting this header instructs Nginx to disable fastcgi_buffering and disable gzip for this request.
            header('X-Accel-Buffering: no');
            // Display a waiting page while we process data. This file contains the flushing logic.
            include VIEWS . 'waiting_page.html.php';
        } elseif ($action == 'leave') {
            // heavy processing is done, let the browser refresh the page
            echo '<meta http-equiv="refresh" content="0">';
            exit;
        } elseif ($action == 'hide') {
            // heavy processing is done, let the browser refresh the page
            echo '<style nonce="' . NONCE . '">.waitingpage .container { display:none; }</style>';
        }
    }
}
