<?php

namespace Buckhill\LaravelSAML\Http\Controllers;

use Illuminate\Http\Request;
use function AuthStack\SP\authn;
use AuthStack\SP\ServiceProvider;
use Illuminate\Routing\Controller;
use function AuthStack\SP\metadata;
use AuthStack\SP\AttributeCollection;
use Buckhill\LaravelSAML\Facade\AuthenticatedUser;
use LightSaml\Binding\BindingFactory;
use LightSaml\Context\Profile\MessageContext;

class SSO extends Controller
{
    /**
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function metadata()
    {
        $metadata = metadata(config('sso'));

        $xml = $metadata->toXml();

        return response($xml, 200, ['Content-Type' => 'application/xml']);
    }

    /**
     * @param Request $request
     */
    public function authn(Request $request)
    {
        if(!$request->has('idp'))
        {
            $ssoConfig = config('sso');

            if(empty($ssoConfig))
            {
                throw new \Exception('SSO config missing - check that you have {project_path}/config/sso.php.');
            }

            if(empty($ssoConfig['IdP']))
            {
                throw new \Exception('IDP configuration empty - check {project_path}/config/sso.php.');
            }

            if(array_key_exists('IdP', $ssoConfig['SP']))
            {
                if(array_key_exist(sha1($ssoConfig['SP']['IdP']), $ssoConfig['IdP']))
                {
                    $idpData = $ssoConfig['IdP'][sha1($ssoConfig['SP']['IdP'])];
                }

                throw new \Exception(sprintf('Could not find configured default IDP [%s] (SP.IdP) in the IdPs array', $ssoConfig['SP']['IdP']));
            }
            else
            {
                $idpData = reset($ssoConfig['IdP']);
            }


            if(empty($idpData['entityId']))
            {
                throw new \Exception('Missing entityId entry inside of IdP configuration - check {project_path}/config/sso.php.');
            }

            $idp = $idpData['entityId'];
        }
        else
        {
            $idp = $request->get('idp');
        }

        $sp = new ServiceProvider(config('sso'), $idp);

        $authn = $sp->authn();

        if($request->has('RelayState'))
        {
            $authn->getAuthN()->setRelayState($request->input('RelayState'));
        }

        $authn->initiate();

        exit;
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function acs(Request $request)
    {
        $issuer = $this->getIssuer($request);

        $collection = authn(config('sso'), $issuer, $request)->consume();

        $remapAttributes = array_key_exists('attributes', config(sprintf('sso.IdP.%s', sha1($issuer)))) && is_array(config(sprintf('sso.IdP.%s.attributes', sha1('issuer'))));

        $attributeMap = $remapAttributes ? config(sprintf('sso.IdP.%s.attributes', sha1($issuer))) : [];

        if(!empty($attributeMap))
        {
            $collection = $collection->map($collection, $attributeMap);
        }

        $this->saveToSession($collection);

        $this->afterACS($request, $collection);

        return $this->redirect($request);
    }

    /**
     * @param AttributeCollection $collection
     */
    protected function saveToSession(AttributeCollection $collection)
    {
        session()->put(config('sso.SP.sessionKeyName'), $collection->toArray(true));
    }

    /**
     * @param Request $request
     * @param AttributeCollection $collection
     */
    protected function afterACS(Request $request, AttributeCollection $collection)
    {
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function redirect(Request $request)
    {
        $redirect = $request->input('RelayState', '');

        if(empty($redirect) || $redirect === 'null')
        {
          $redirect = config('sso.SP.redirectAfterSuccess');
        }

        return redirect($redirect);
    }

    protected function getIssuer(Request $request)
    {
        $bindingFactory = new BindingFactory();
        $binding = $bindingFactory->getBindingByRequest($request);

        $messageContext = new MessageContext();

        $binding->receive($request, $messageContext);

        $issuer = $messageContext->getMessage()->getIssuer()->getValue();

        $idps = config('sso.IdP');

        if(!array_key_exists(sha1($issuer), $idps))
        {
            throw new \Exception(sprintf('Invalid SAML request received - Issuer (IdP) [%s] not present in configuration.', $issuer));
        }

        return $issuer;
    }
}
