<?php

namespace HS\Auth\Blackbox;

use HS\PortalLogin;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;

class BlackboxPortalProvider extends EloquentUserProvider
{
    /**
     * Overrides EloquentUserProvider::retrieveByCredentials()
     *
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
            (count($credentials) === 1 &&
                Str::contains($this->firstCredentialKey($credentials), 'password'))) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->newModelQuery();

        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        // @HelpSpot Customization
        $portalUser = $query->first();

        // For BlackBox auth, if we don't have a user in HS_Portal_Login
        // then we create one here. This lets the Laravel auth mechanism
        // work alongside BlackBox auth. Laravel assumes the user
        // exists before logging in, while BlackBox creates a user
        // if one does not exist IF the BlackBox auth is successful.
        if (! $portalUser) {
            // At this point in the flow, we have an sUsername
            // but do not have an sEmail. We create a placeholder
            // user with the correct username, but no email.
            return PortalLogin::create([
                'sEmail' => Str::random(72),
                'sPasswordHash' => '',
                'sUsername' => $credentials['sUsername'] ?? '',
            ]);
        }

        return $portalUser;
    }

    /**
     * Overrides EloquentUserProvider::validateCredentials()
     *
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $canProceed = true;
        $customCodeFile = customCodePath('BlackBoxPortal.php');

        if (! file_exists($customCodeFile)) {
            Log::error('BlackBox is enabled but file '.$customCodeFile.' was not found.');
            errorLog('BlackBox is enabled but file '.$customCodeFile.' was not found.', 'authentication', __FILE__, __LINE__);
            $canProceed = false;
        }

        require_once $customCodeFile;

        if (! function_exists('BlackBoxPortal')) {
            Log::error('BlackBox is enabled but function "BlackBoxPortal" was not found within '.$customCodeFile.' was not found.');
            errorLog('BlackBox is enabled but function "BlackBoxPortal" was not found within file '.$customCodeFile.' was not found.', 'authentication', __FILE__, __LINE__);
            $canProceed = false;
        }

        if ($canProceed) {
            $email = BlackBoxPortal($credentials['sUsername'], $credentials['password']);

            if (! $email) {
                return false;
            }

            // If we authenticated but the email returned does not match
            // the HS_Portal_Login email, then it's likely a new login
            // record with sUsername but not email.
            // We will upsert an HS_Portal_Login user to have both sUsername and sEmail matching
            // this BlackBox authenticated user.
            if ($user->sEmail != $email) {
                // If a user exists already with this email, but there is no sUsername (isn't $user)
                //       then this is a "legacy" user (before HS5 and this bug fix). In that case,
                //       we assign the $user the email (it already has the username) and delete the $existingUser
                $existingUser = PortalLogin::getByEmail($email);

                if ($existingUser && $existingUser->xLogin != $user->xLogin) {
                    $user->sEmail = $email;
                    $existingUser->delete(); // do this before saving $user
                    $user->save();
                } else {
                    // User doesn't exist yet with this email, or
                    // $user and $existinguser are the same user
                    $user->sEmail = $email;
                    $user->save(); // Assumes an Eloquent user
                }
            }

            return $email;
        }

        // Fall back to internal HelpSpot authentication
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }
}
