<?php

declare(strict_types=1);

namespace SimpleSAML\Module\exampleauth\Controller;

use SimpleSAML\Auth;
use SimpleSAML\Configuration;
use SimpleSAML\Error;
use SimpleSAML\HTTP\RunnableResponse;
use SimpleSAML\Module\exampleauth\Auth\Source\External;
use SimpleSAML\Session;
use SimpleSAML\Utils;
use SimpleSAML\XHTML\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;

use function array_key_exists;
use function preg_match;
use function session_id;
use function session_start;
use function urldecode;

/**
 * Controller class for the exampleauth module.
 *
 * This class serves the different views available in the module.
 *
 * @package simplesamlphp/simplesamlphp
 */
class ExampleAuth
{
    /** @var \SimpleSAML\Configuration */
    protected Configuration $config;

    /** @var \SimpleSAML\Session */
    protected Session $session;

    /**
     * @var \SimpleSAML\Auth\State|string
     * @psalm-var \SimpleSAML\Auth\State|class-string
     */
    protected $authState = Auth\State::class;


    /**
     * Controller constructor.
     *
     * It initializes the global configuration and session for the controllers implemented here.
     *
     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
     * @param \SimpleSAML\Session $session The session to use by the controllers.
     *
     * @throws \Exception
     */
    public function __construct(
        Configuration $config,
        Session $session
    ) {
        $this->config = $config;
        $this->session = $session;
    }


    /**
     * Inject the \SimpleSAML\Auth\State dependency.
     *
     * @param \SimpleSAML\Auth\State $authState
     */
    public function setAuthState(Auth\State $authState): void
    {
        $this->authState = $authState;
    }


    /**
     * Auth testpage.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
     *
     * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse
     */
    public function authpage(Request $request)
    {
        /**
         * This page serves as a dummy login page.
         *
         * Note that we don't actually validate the user in this example. This page
         * just serves to make the example work out of the box.
         */
        $returnTo = $request->request->get('ReturnTo');
        if ($returnTo === null) {
            throw new Error\Exception('Missing ReturnTo parameter.');
        }

        $httpUtils = new Utils\HTTP();
        $returnTo = $httpUtils->checkURLAllowed($returnTo);

        /**
         * The following piece of code would never be found in a real authentication page. Its
         * purpose in this example is to make this example safer in the case where the
         * administrator of the IdP leaves the exampleauth-module enabled in a production
         * environment.
         *
         * What we do here is to extract the $state-array identifier, and check that it belongs to
         * the exampleauth:External process.
         */
        if (!preg_match('@State=(.*)@', $returnTo, $matches)) {
            throw new Error\Exception('Invalid ReturnTo URL for this example.');
        }

        /**
         * The loadState-function will not return if the second parameter does not
         * match the parameter passed to saveState, so by now we know that we arrived here
         * through the exampleauth:External authentication page.
         */
        $this->authState::loadState(urldecode($matches[1]), 'exampleauth:External');

        // our list of users.
        $users = [
            'student' => [
                'password' => 'student',
                'uid' => 'student',
                'name' => 'Student Name',
                'mail' => 'somestudent@example.org',
                'type' => 'student',
            ],
            'admin' => [
                'password' => 'admin',
                'uid' => 'admin',
                'name' => 'Admin Name',
                'mail' => 'someadmin@example.org',
                'type' => 'employee',
            ],
        ];

        // time to handle login responses; since this is a dummy example, we accept any data
        $badUserPass = false;
        if ($request->getMethod() === 'POST') {
            $username = $request->request->get('username');
            $password = $request->request->get('password');

            if (!isset($users[$username]) || $users[$username]['password'] !== $password) {
                $badUserPass = true;
            } else {
                $user = $users[$username];

                $session = new SymfonySession();
                if (!$session->getId()) {
                    $session->start();
                }

                $session->set('uid', $user['uid']);
                $session->set('name', $user['name']);
                $session->set('mail', $user['mail']);
                $session->set('type', $user['type']);

                return new RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$returnTo]);
            }
        }

        // if we get this far, we need to show the login page to the user
        $t = new Template($this->config, 'exampleauth:authenticate.twig');
        $t->data['badUserPass'] = $badUserPass;
        $t->data['returnTo'] = $returnTo;

        return $t;
    }


    /**
     * Redirect testpage.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
     *
     * @return \SimpleSAML\HTTP\RunnableResponse
     */
    public function redirecttest(Request $request): RunnableResponse
    {
        /**
         * Request handler for redirect filter test.
         */
        $stateId = $request->query->get('StateId');
        if ($stateId === null) {
            throw new Error\BadRequest('Missing required StateId query parameter.');
        }

        /** @var array $state */
        $state = $this->authState::loadState($stateId, 'exampleauth:redirectfilter-test');
        $state['Attributes']['RedirectTest2'] = ['OK'];

        return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
    }


    /**
     * Resume testpage.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
     *
     * @return \SimpleSAML\HTTP\RunnableResponse
     */
    public function resume(Request $request): RunnableResponse
    {
        /**
         * This page serves as the point where the user's authentication
         * process is resumed after the login page.
         *
         * It simply passes control back to the class.
         */
        return new RunnableResponse([External::class, 'resume'], [$request]);
    }
}