<?php

/*
 * Copyright 2005 - 2025 Centreon (https://www.centreon.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For more information : contact@centreon.com
 *
 */

declare(strict_types=1);

namespace Core\Infrastructure\Common\Api;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;

/**
 * Override symfony router to generate base URI.
 */
class Router implements RouterInterface, RequestMatcherInterface, WarmableInterface
{
    use HttpUrlTrait;

    /** @var RouterInterface */
    private RouterInterface $router;

    /** @var RequestMatcherInterface */
    private RequestMatcherInterface $requestMatcher;

    /**
     * MyRouter constructor.
     *
     * @param RouterInterface $router
     * @param RequestMatcherInterface $requestMatcher
     */
    public function __construct(RouterInterface $router, RequestMatcherInterface $requestMatcher)
    {
        $this->router = $router;
        $this->requestMatcher = $requestMatcher;
    }

    /**
     * Get router.
     *
     * @return RouterInterface
     */
    public function getRouter(): RouterInterface
    {
        return $this->router;
    }

    /**
     * {@inheritDoc}
     *
     * @param string $name
     * @param array{
     *   base_uri?: string,
     *   ...<string, mixed>
     * } $parameters
     * @param int $referenceType
     *
     * @throws \Exception
     *
     * @return string
     */
    public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
    {
        $parameters['base_uri'] ??= $this->getBaseUri();
        $doubleBaseUri = '';
        if (! empty($parameters['base_uri'])) {
            $doubleBaseUri = $parameters['base_uri'] . '/' . $parameters['base_uri'];
            $doubleBaseUri = $this->cleanPrefixUrl($doubleBaseUri);
            $parameters['base_uri'] .= '/';
        }

        // Manage URL Generation for HTTPS and Legacy nested route generation calls
        $context = $this->router->getContext();
        $forwardedProtoHeader = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null;
        if ($forwardedProtoHeader) {
            // It could be possible that HTTP_X_FORWARDED_PROTO contains multiple comma-separated values e.g "https,http"
            $proto = mb_strtolower(trim(explode(',', $forwardedProtoHeader)[0]));
            $scheme = $proto === 'https' ? 'https' : 'http';
            $context = $this->router->getContext();
            $context->setScheme($scheme);
        } elseif ($_SERVER['REQUEST_SCHEME'] === 'https') {
            $context->setScheme($_SERVER['REQUEST_SCHEME']);
        }

        $generatedRoute = $this->router->generate($name, $parameters, $referenceType);
        $generatedRoute = $this->cleanPrefixUrl($generatedRoute);

        // remove double identical prefixes due to progressive migration
        $generatedRoute = str_replace($doubleBaseUri, $parameters['base_uri'], $generatedRoute);

        // remove double slashes
        return $this->cleanPrefixUrl($generatedRoute);
    }

    /**
     * {@inheritDoc}
     *
     * @param RequestContext $context
     */
    public function setContext(RequestContext $context): void
    {
        $this->router->setContext($context);
    }

    /**
     * @inheritDoc
     */
    public function getContext(): RequestContext
    {
        return $this->router->getContext();
    }

    /**
     * @inheritDoc
     */
    public function getRouteCollection(): RouteCollection
    {
        return $this->router->getRouteCollection();
    }

    /**
     * {@inheritDoc}
     *
     * @return array<string,mixed>
     */
    public function match(string $pathinfo): array
    {
        return $this->router->match($pathinfo);
    }

    /**
     * {@inheritDoc}
     *
     * @return array<string,mixed>
     */
    public function matchRequest(Request $request): array
    {
        return $this->requestMatcher->matchRequest($request);
    }

    /**
     * @param string $cacheDir
     * @param null|string $buildDir
     *
     * @return string[]
     */
    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        return [];
    }

    /**
     * Create a href to a legacy page.
     *
     * @param int $topologyPage
     * @param array<string, mixed> $options
     *
     * @return string
     */
    public function generateLegacyHref(int $topologyPage, array $options = []): string
    {
        return $options === []
            ? $this->getBaseUrl() . '/main.php?p=' . $topologyPage
            : $this->getBaseUrl() . '/main.php?p=' . $topologyPage . '&' . http_build_query($options);
    }

    /**
     * Generate a URL for internal API calls using localhost.
     * This avoids issues with proxies, load balancers, and HTTPS termination.
     *
     * @param string $name Route name
     * @param array<string, mixed> $parameters Route parameters
     *
     * @throws \Exception
     *
     * @return string The local URL pointing to 127.0.0.1
     */
    public function generateLocalUrl(string $name, array $parameters = []): string
    {
        $context = $this->router->getContext();

        // Save original context values
        $originalHost = $context->getHost();
        $originalScheme = $context->getScheme();
        $originalHttpPort = $context->getHttpPort();

        // Force local context for internal calls
        $context->setHost('127.0.0.1');
        $context->setScheme('http');
        $context->setHttpPort(80);

        try {
            $parameters['base_uri'] ??= $this->getBaseUri();
            if (! empty($parameters['base_uri'])) {
                $parameters['base_uri'] .= '/';
            }

            return $this->generate($name, $parameters, self::ABSOLUTE_URL);
        } finally {
            // Restore original context
            $context->setHost($originalHost);
            $context->setScheme($originalScheme);
            $context->setHttpPort($originalHttpPort);
        }
    }

    /**
     * @param string $url
     *
     * @throws \RuntimeException
     *
     * @return string
     */
    private function cleanPrefixUrl(string $url): string
    {
        $result = preg_replace('/(?<!:)\.?(\/+)/', '/', $url);
        if ($result === null) {
            throw new \RuntimeException('Error occurred during regular expression search and replace.');
        }

        return $result;
    }
}
