<?php

namespace HS\Http\Controllers\Auth;

use HS\User;
use HS\PortalLogin;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Cookie;

use Aacotroneo\Saml2\Saml2Auth;
use Aacotroneo\Saml2\Events\Saml2LoginEvent;
use Aacotroneo\Saml2\Http\Controllers\Saml2Controller;

class SamlController extends Saml2Controller
{
    public function error()
    {
        $errors = session('saml-error');

        return view('auth.saml-error', [
            'samlErrors' => $errors,
            'debug' => config('app.debug'),
        ]);
    }

    /**
     * Process an incoming saml2 assertion request.
     * Fires 'Saml2LoginEvent' event if a valid user is found.
     *
     * NOTE: This is copy/paste from the base class, with additions
     *       as noted below
     * @param Saml2Auth $saml2Auth
     * @param $idpName
     * @return \Illuminate\Http\Response
     */
    public function acs(Saml2Auth $saml2Auth, $idpName)
    {
        $errors = $saml2Auth->acs();

        if (!empty($errors)) {
            logger()->error('Saml2 error_detail', ['error' => $saml2Auth->getLastErrorReason()]);
            session()->flash('saml2_error_detail', [$saml2Auth->getLastErrorReason()]);

            logger()->error('Saml2 error', $errors);
            session()->flash('saml2_error', $errors);
            return redirect(config('saml2_settings.errorRoute')); // todo: Different route if debug is on?
        }
        $user = $saml2Auth->getSaml2User();

        event(new Saml2LoginEvent($idpName, $user, $saml2Auth));

        // BEGIN HELPSPOT CUSTOMIZATION
        $response = $this->authenticatedUser($user, $saml2Auth->getLastMessageId());

        if( $response )
        {
            return $response;
        }
        // END HELPSPOT CUSTOMIZATION

        $redirectUrl = $user->getIntendedUrl();

        if ($redirectUrl !== null) {
            return redirect($redirectUrl);
        } else {
            return redirect(config('saml2_settings.loginRoute'));
        }
    }

    public function authenticatedUser($user, $lastMessageId) {
        // Prevent replay attacks
        $hasReceivedMessageKey = sprintf('saml::message::%s', $lastMessageId);
        if (Cache::has($hasReceivedMessageKey)) {
            return abort(401, "Invalid SAML Authentication");
        } else {
            // ~ 1 month. Ideally is "forever" except memory constraints exists.
            Cache::put($hasReceivedMessageKey, true, 2628000);
        }

        $userData = [
            'id' => $user->getUserId(),
            'attributes' => $user->getAttributes(),
            'assertion' => $user->getRawSamlAssertion()
        ];

        // Default to search for user by their given ID
        $userQuery = $userData['id'];
        $identities = null;

        // If their userdata has attributes, we'll find a username or email from there
        if( is_array($userData['attributes']) && count($userData['attributes']) > 0 )
        {
            $validIdentityClaims = [
                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name',
                'mail', // See GitHub issue #614
            ];

            $identities = collect($userData['attributes'])->filter(function($item, $key) use($validIdentityClaims) {
                return in_array($key, $validIdentityClaims);
            })->mapWithKeys(function ($item, $key) {
                return [basename($key) => $item[0]];
            })->toArray();

            // Priority: name, emailaddress, email, mail
            //           "email" and "mail" aren't technically valid but they seem to get used
            if( isset($identities['name']) ) {
                $userQuery = $identities['name'];
            } elseif( isset($identities['emailaddress']) ) {
                $userQuery = $identities['emailaddress'];
            } elseif( isset($identities['email']) ) {
                $userQuery = $identities['email'];
            } elseif( isset($identities['mail']) ) {
                $userQuery = $identities['mail'];
            }
        }

        $helpspotUser = User::where('sUsername', $userQuery)
            ->first();

        // We have an admin user, return null so head to
        // SAML intended redirect ('/admin/relay')
        if( $helpspotUser ) {
            auth()->login($helpspotUser);
            return null;
        }

        // If not a staffer, log them in as a "customer" on the portal end
        $portalUser = PortalLogin::where('sUsername', $userQuery)
            ->orWhere('sEmail', $userQuery)
            ->first();

        if( $portalUser ) {
            // If the portal user already exists in HelpSpot's database, log them in
            $this->loginPortalUser($portalUser);
            return null; // redirect()->to(cHOST.'/index.php?pg=request.history');
        } else {
            // Else create the portal user, then log them in
            $data = [
                'sPasswordHash' => Hash::make(Str::random(24)),
            ];

            // Username is either the "name" attribute or whatever attribute
            // we parse out to query against (which may be the same as their email)
            $data['sUsername'] = (isset($identities['name']))
                ? $identities['name']
                : $userQuery;

            // Email is a bit of a guess. $userQuery may be their email - we assume
            // so if it includes an "@" symbol. Else we set it to be the same as their
            // sUsername from above, which means it'll be their "name" attribute, or
            // $userQuery once again.
            $data['sEmail'] = (strpos($userQuery, '@') !== false)
                ? $userQuery
                : $data['sUsername'];

            try {
                $portalUser = PortalLogin::create($data);
                $this->loginPortalUser($portalUser);
                return null; // redirect()->to(cHOST.'/index.php?pg=request.history');
            } catch(\Exception $e) {
                // Log error and fall through to saml error page
                Log::error($e, ['identities' => $identities, 'userdata' => $userData, 'userquery' => $userQuery,]);
            }
        }

        // No admin user and could not find or create portal user
        session()->flash('saml-error', [
            'attempted' => $userQuery,
            'attributes' => array_merge([
                'nameId' => $userData['id'],
                'attributes' => $userData['attributes']
            ])
        ]);
        return redirect()->route('saml2_error', 'hs');
    }

    protected function loginPortalUser($portalUser) {
        auth('portal')->login($portalUser);

        //this is the username if we're in black box mode
        if (hs_setting('cHD_PORTAL_LOGIN_AUTHTYPE') == 'blackbox') {
            session()->put('login_username', $_POST['login_email']);
        }
        session()->put('login_sEmail', $portalUser->sEmail);
        session()->put('login_xLogin', $portalUser->xLogin);
        session()->put('login_ip', hs_clientIP());
    }

    /**
     * Initiate a login request.
     *
     * @param Saml2Auth $saml2Auth
     */
    public function login(Saml2Auth $saml2Auth)
    {
        $intended = Cookie::get('intended-url');

        if ($intended) {
            Cookie::queue(Cookie::forget('intended-url'));
        } else {
            $intended = config('saml2_settings.loginRoute');
        }

        $saml2Auth->login($intended);
    }

}
