<?php

namespace PrestaShop\Module\LCVPrestaConnector\Controller;
 
use Symfony\Bundle\SecurityBundle\Security;

use PrestaShop\Module\LCVPrestaConnector\OidcAuthenticator;
use PrestaShopBundle\Security\Admin\EmployeeProvider;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use PrestaShopBundle\Service\Routing\Router as PrestaShopRouter;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
 * Contrôleur admin du flow SSO OIDC (backdoor équipe technique).
 *
 * Anonyme (cf. _anonymous_controller: true dans routes.yml et
 * isAuthenticationRequired() ci-dessous) car il doit fonctionner avant
 * qu'une session admin soit établie — c'est précisément le but du SSO.
 *
 * Le paramètre 'step' aiguille vers l'une des deux phases du flow :
 *   - step=login    (défaut) : démarre le flow, redirige vers Authentik
 *   - step=callback          : reçoit le code Authentik, valide et ouvre la session
 *
 * On regroupe les deux étapes dans un seul contrôleur car :
 *   - elles partagent toute leur infrastructure (instance OidcAuthenticator,
 *     vérification du toggle, gestion d'erreur, logging) ;
 *   - cela limite la surface exposée à une seule URL publique ;
 *   - cela simplifie la configuration côté Authentik (un seul redirect_uri).
 *
 * La protection est assurée par OidcAuthenticator (état CSRF en session,
 * validation crypto de l'id_token au callback, whitelist stricte du domaine
 * email).
 */
class OidcAdminControllerV9 extends AdminController
{
    public function __construct(
        private readonly EmployeeProvider         $employeeProvider,
        private readonly Security $security,
        private readonly CsrfTokenManagerInterface $csrfTokenManager,
    ) {
        parent::__construct();
    }

    /**
     * Pas d'authentification admin requise — le but du SSO est précisément
     * de pouvoir s'authentifier sans avoir de session.
     */
    protected function isAuthenticationRequired(): bool
    {
        return false;
    }
    
    /**
     * Point d'entrée du login SSO OIDC (redirection vers Authentik puis callback).
     * 
     * @return Response
     */
    public function oidcAction(Request $request) : Response
    {
        try
        {
            $auth = new OidcAuthenticator($this->module);

            if (!$auth->isEnabled())
                return new Response('SSO désactivé.', 403);

            $step = $request->query->get('step', 'login');

            switch ($step)
            {
                case 'login':
                    return $this->doLogin($auth);

                case 'callback':
                    return $this->doCallback($auth);

                default:
                    return new Response('Étape SSO inconnue.', 400);
            }
        }
        catch (\Throwable $e)
        {
            \PrestaShopLogger::addLog(
                'LCVPrestaConnector SSO : erreur fatale dans le contrôleur : ' . $e->getMessage(), 3
            );

            return new Response(
                '<!doctype html><html><head><meta charset="utf-8"><title>Erreur SSO</title></head><body>'
                . '<h1>Erreur lors du SSO</h1>'
                . '<p>' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . '</p>'
                . '</body></html>',
                500,
                ['Content-Type' => 'text/html; charset=utf-8']
            );
        }
    }

    /**
     * Étape 1 : démarre le flow OIDC, redirige vers Authentik.
     * Ne retourne pas une vraie Response — la lib jumbojett appelle exit()
     * après avoir envoyé un header Location:.
     */
    private function doLogin(OidcAuthenticator $auth) : Response
    {        
        $auth->startLoginFlow();

        // Inatteignable en théorie
        return new Response('Redirection vers Authentik...', 302);
    }

    /**
     * Étape 2 : reçoit le retour Authentik (code + state), valide tout,
     * ouvre/promeut le compte admin local et établit la session admin.
     */
    private function doCallback(OidcAuthenticator $auth) : Response
    {
        try
        {
            // Validation complète : state, code, id_token, JWKS, email_verified, domaine
            $userInfo = $auth->handleCallback();

            // Get-or-create + promote du compte admin local
            $employee = $auth->getOrCreateAdminEmployee();

            // On vide le cookie legacy avant d'établir la session Symfony.
            // Sans ça, un vieux cookie de session précédente est encore présent
            // et LegacyRouterChecker::isLoggedBack() le lit, puis appelle
            // getDefaultShopID() sur un employee dont associated_shops est vide
            // → Warning: Undefined array key 0 → crash.
            \Context::getContext()->cookie->logout();

            // Pose du cookie admin (équivalent processLogin / authenticateEmployee
            // d'AdminLoginController), avec expiration absolue à +1h.
            $url = $this->openAdminSession($employee, $userInfo['username']);            
            
            // Redirection vers le tableau de bord du module      
            return new Response(
                '<!doctype html><html><head><meta charset="utf-8">'
                . '<meta http-equiv="refresh" content="0;url=' . htmlspecialchars($url, ENT_QUOTES) . '">'
                . '<title>Connexion en cours...</title></head><body>'
                . '<p>Connexion réussie, redirection vers le backoffice...</p>'
                . '<p><a href="' . htmlspecialchars($url, ENT_QUOTES) . '">Cliquer ici si la redirection ne se fait pas.</a></p>'
                . '</body></html>',
                200,
                ['Content-Type' => 'text/html; charset=utf-8']
            );
        }
        catch (\Throwable $e)
        {
            \PrestaShopLogger::addLog(
                'LCVPrestaConnector SSO : échec callback : ' . $e->getMessage(), 3
            );

            return new Response(
                '<!doctype html><html><head><meta charset="utf-8"><title>Connexion SSO refusée</title></head><body>'
                . '<h1>Connexion SSO refusée</h1>'
                . '<p>' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . '</p>'
                . '<p><a href="javascript:history.back()">Retour</a></p>'
                . '</body></html>',
                403,
                ['Content-Type' => 'text/html; charset=utf-8']
            );
        }
    }

    /**
     * Établit la session admin Prestashop pour cet employee via Symfony Security.
     *
     * PS 9 : dispatch AuthenticationTokenCreatedEvent → EmployeeSessionSubscriber
     *        crée l'EmployeeSession en base et l'attache au token, exactement
     *        comme le fait FormLoginAuthenticator lors d'un login normal.
     *
     * PS 8 : Adapter\Security\Admin::onKernelRequest() lit le cookie legacy
     *        (isLoggedBack) et crée le token Symfony lui-même — il suffit de
     *        poser correctement le cookie.
     *
     * @throws \RuntimeException si l'employee n'est pas rattaché à une boutique
     */
    private function openAdminSession(\Employee $employee, string $authenticatedUserName): string
    {
        $associatedShops = $employee->getAssociatedShops();
        if (empty($associatedShops) && !$employee->isSuperAdmin())
            throw new \RuntimeException('Cet employé n\'est plus rattaché à aucune boutique.');
 
        \PrestaShopLogger::addLog(
            sprintf('%sLCVPrestaConnector SSO : connexion depuis %s (via SSO %s, user=%s)',
                $this->module->getBridge()->getId(),
                \Tools::getRemoteAddr(), 
                $this->module->getBridge()->getId(),
                $authenticatedUserName),
            1, null, '', 0, true, (int) $employee->id
        );
         
        // Charge l'entité Doctrine Employee (≠ legacy \Employee)
        $user = $this->employeeProvider->loadUserByIdentifier($employee->email);

        $this->security->login($user, firewallName: 'main');         

        $token = $this->csrfTokenManager
                    ->getToken($user->getUserIdentifier())
                    ->getValue();
                    
        $context = \Context::getContext();
        $context->employee = $employee;

        $url = $this->generateUrl($this->module->name . '_configuration');    
        $url = PrestaShopRouter::generateTokenizedUrl($url, $token);

        return $url;
    }
}