<?php

/**
 * Class IDService
 *
 * @package Mtc\Plugins\LexisNexisIDU\Classes
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 */
namespace Mtc\Plugins\LexisNexisIDU\Classes;

use Carbon\Carbon;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Item;
use Mtc\Shop\Order;

/**
 * Class IDService
 *
 * Service that will perform the ID check on the user
 *
 * @package Mtc\Plugins\LexisNexisIDU\Classes
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 */
class IDService
{
    /** Set constant for failed ID check */
    const ID_CHECK_PASSED = 1;

    /** Set constant for passed ID check */
    const ID_CHECK_FAILED = -1;

    /** Set constant for passed ID check */
    const ID_CHECK_FAILED_CASCADE = -2;

    /** Set endpoint constant for ID check */
    const ENDPOINT_LIVE = 'https://ws-idu.tracesmart.co.uk/v5.8/index.php?wsdl';

    /** Set endpoint constant for ID check */
    const ENDPOINT_TEST = 'https://sandbox.ws-idu.tracesmart.co.uk/v5.8/?wsdl';

    /** Max allowed ID checks in session */
    const MAX_ALLOWED_ID_CHECKS_IN_SESSION = 4;

    /**
     * Review the basket to make sure ID check will happen when it is necessary
     * This is done once per user and only on products that are in specific categories
     *
     * @param \Basket $basket Users basket
     */
    public static function reviewBasket(\Basket $basket)
    {
        // Pluck all product IDs from the basket
        $basket_item_ids = collect($basket->items)->pluck('item_id');

        $member = \Mtc\Modules\Members\Classes\Auth::getLoggedInMember();

        if ($member->id && $member->passed_id_check != null) {
            $basket->require_id_check = false;

            return;
        }

        $basket->require_id_check = self::basketItemsRequireIDCheck($basket_item_ids);

        if (! $basket->require_id_check && ! empty($_SESSION['ID_CHECK_STATUS'])) {
            $_SESSION['ID_CHECK_STATUS'] = null;
        }

        if (
            ! empty($_SESSION['ID_CHECK_COUNT'])
            && $_SESSION['ID_CHECK_COUNT'] >= self::MAX_ALLOWED_ID_CHECKS_IN_SESSION
        ) {
            $basket->general_error = 'Unfortunately we have been unable to verify your ID with the information provided. Please contact us and we can try to verify your ID over the phone';
        }

        // Check if we need to enforce ID check as well
        if (isset($_SESSION['ID_CHECK_STATUS']) && $_SESSION['ID_CHECK_STATUS'] === self::ID_CHECK_FAILED) {
            $basket->require_id_check_cascade = 1;
        }
    }

    /**
     * Run the ID check on an order when it is created
     *
     * @param Order $order newly created order
     */
    public static function runCheck(Order $order, $live_run = true)
    {
        // Check if we need to run cascade check with additional info
        $cascade = in_array($_SESSION['ID_CHECK_STATUS'] ?? null, [
            self::ID_CHECK_FAILED,
            self::ID_CHECK_FAILED_CASCADE,
        ]);
        // clear ID check status to avoid hitting this again when not needed
        $_SESSION['ID_CHECK_STATUS'] = null;

        // get current member
        $member = \Mtc\Modules\Members\Classes\Auth::getLoggedInMember();

        // We need to disable this on staging to prevent from doing unnecessary tests
        // and spending test credits. It is also very likely that on staging data
        // will be incorrect so we're just giving a free-for-all here
        if ($live_run && ! in_array(config('app.env'), ['demo', 'production'])) {
            return;
        }

        // No need to do the ID check when it has already been passed
        if ($member->id && $member->passed_id_check != null) {
            if (! $live_run) {
                echo "Member #{$member->id}: members.passed_id_check is TRUE.\n";
            }
            $order->id_check_confirmed = 2;
            $order->save();

            return;
        }

        // When redirecting, don't bother doing another check
        if (
            isset($_SESSION['ID_CHECK_REDIRECTING'])
            && $_SESSION['ID_CHECK_REDIRECTING'] === true
            && $_SESSION['ID_CHECK_STATUS'] === self::ID_CHECK_FAILED_CASCADE
        ) {
            $order->failed_id_check = Carbon::now()->format('Y-m-d H:i:s');
            $order->save();

            return;
        }

        // If user is not logged in we need to check if this email address already doesn't
        // match up a user that already has passed ID check
        if (! $member->id) {
            // Find user with order email that has passed ID check
            $email_passed = Member::query()
                ->where('email', $order->info->email)
                ->where('passed_id_check', 1)
                ->count() > 0;

            // If found we don't need to verify user
            if ($email_passed) {
                $order->id_check_confirmed = 2;
                $order->save();

                return;
            }
        }

        // ID checks are only supported in UK so we can't run if billing address doesn't match
        if ($order->billingAddress->country !== 'GB') {
            return;
        }

        $require_id_check = self::basketItemsRequireIDCheck(collect($order->items)->pluck('item_id'));

        if (! $live_run) {
            echo "ID check required.\n";
        }

        if (! $require_id_check) {
            return;
        }

        if (! $live_run) {
            echo "About to runApiCall(). Cascade is [{$cascade}].\n";
        }

        // Run the ID check and store its result in Session
        $api_result = self::runApiCall($order, $cascade);

        if (! $live_run) {
            var_dump($api_result);
            echo 'Exiting from '.__METHOD__."\n";
            exit();
        }

        if ($api_result == self::ID_CHECK_FAILED_CASCADE || $api_result == self::ID_CHECK_FAILED) {
            $order->failed_id_check = Carbon::now()->format('Y-m-d H:i:s');
            $order->save();
        } elseif ($api_result == self::ID_CHECK_PASSED) {
            if ($member) {
                $member->passed_id_check = Carbon::now()->format('Y-m-d H:i:s');
                $member->save();
            }
            $order->id_check_confirmed = 1;
            $order->save();
        }

        $_SESSION['ID_CHECK_STATUS'] = $api_result;
    }

    /**
     *
     * Check if any of these products are doctor products
     * that will require an ID check
     * If so we will require extra fields like DOB to be shown
     *
     * @param $item_ids
     * @return bool
     */
    private static function basketItemsRequireIDCheck($item_ids)
    {
        $doctor_items = Item::query()
            ->whereIn('id', $item_ids)
            ->whereIn('product_type', ['doctor'])
            ->count();

        $pharmacy_items_requiring_id_check = Item::query()
            ->whereIn('id', $item_ids)
            ->where('id_check_required', true)
            ->count();

        return ($doctor_items + $pharmacy_items_requiring_id_check) > 0;
    }

    /**
     * Make the API call to tracesmart service (
     * @param $order
     * @return mixed
     */
    private static function runApiCall($order, $cascade = false)
    {
        if (empty($_SESSION['ID_CHECK_COUNT'])) {
            $_SESSION['ID_CHECK_COUNT'] = 0;
        }

        if ($_SESSION['ID_CHECK_COUNT'] >= self::MAX_ALLOWED_ID_CHECKS_IN_SESSION) {
            return self::ID_CHECK_FAILED_CASCADE;
        }
        $_SESSION['ID_CHECK_COUNT']++;
        // Initialize variables
        $params = new \stdClass;
        $params->Login = new \stdClass;
        $params->IDU = new \stdClass;
        $params->Person = new \stdClass;
        $params->Person->cardavs = new \stdClass;
        $params->Person->cardavs->CardAddress = new \stdClass;
        $params->Services = new \stdClass;

        if (DEV_MODE || LN_IDU_TEST === true) {
            /*
             * TEST credentials
             */
            $endpoint = self::ENDPOINT_TEST;
            $client = new \SoapClient(
                $endpoint,
            );
            $params->Login->username = LN_IDU_USERNAME_TEST;
            $params->Login->password = LN_IDU_PASSWORD_TEST;
        } else {
            /*
             * LIVE credentials
             */
            $endpoint = self::ENDPOINT_LIVE;
            $client = new \SoapClient(
                $endpoint,
            );
            $params->Login->username = LN_IDU_USERNAME;
            $params->Login->password = LN_IDU_PASSWORD;
        }

        // Reference is optional/mandatory based on user settings
        $params->IDU->Reference = $order->id;

        // ID and IKey should be passed to continue a previous search
        $params->IDU->ID = '';
        $params->IDU->IKey = '';
        $params->IDU->Scorecard = 'IDU Default';
        $params->IDU->equifaxUsername = '';
        $params->IDU->GlobalTransactionId = '';

        $dob_string = $order->info->dob;

        // Subject details
        $params->Person->uklexid = '';
        $params->Person->uklexidasofdate = '';
        $params->Person->forename = $order->billingAddress->firstname;
        $params->Person->middle = $order->billingAddress->middle_name ?? '';
        $params->Person->surname = $order->billingAddress->lastname;
        $params->Person->gender = $order->billingAddress->gender[0] ?? 'M';
        $params->Person->dob = $dob_string;

        // Subject address details
        $params->Person->address1 = $order->billingAddress->address1;
        if (! empty($order->billingAddress->address2)) {
            $params->Person->address2 = $order->billingAddress->address2;
            $params->Person->address3 = $order->billingAddress->city;
        } else {
            $params->Person->address2 = $order->billingAddress->city;
            $params->Person->address3 = '';
        }
        $params->Person->address4 = '';
        $params->Person->address5 = '';
        $params->Person->address6 = '';
        $params->Person->postcode = $order->billingAddress->postcode;

        $temp_passport = '';
        if ($cascade && $_SESSION['ID_CHECK_TYPE'] === 'passport') {
            $temp_passport = $_SESSION['ID_CHECK_NUMBER'];
        }
        // E.g.: L652845638GBR7604119M2201018B85475BB<<<<<<06
        $params->Person->passport1 = substr($temp_passport, 0, 9);
        $params->Person->passport2 = substr($temp_passport, 9, 1);
        $params->Person->passport3 = substr($temp_passport, 10, 3);
        $params->Person->passport4 = substr($temp_passport, 13, 7);
        $params->Person->passport5 = substr($temp_passport, 20, 1);
        $params->Person->passport6 = substr($temp_passport, 21, 7);
        $params->Person->passport7 = substr($temp_passport, 28, 14);
        $params->Person->passport8 = substr($temp_passport, 42, 2);

        $params->Person->travelvisa1 = '';
        $params->Person->travelvisa2 = '';
        $params->Person->travelvisa3 = '';
        $params->Person->travelvisa4 = '';
        $params->Person->travelvisa5 = '';
        $params->Person->travelvisa6 = '';
        $params->Person->travelvisa7 = '';
        $params->Person->travelvisa8 = '';
        $params->Person->travelvisa9 = '';

        $temp_idcard = '';
        if ($cascade && $_SESSION['ID_CHECK_TYPE'] === 'id_card') {
            $temp_idcard = $_SESSION['ID_CHECK_NUMBER'];
        }
        // E.g.: IRGBR1234567897<<<<<<<<<<<<<<<5605248F2201018GBR<<<<<<<<<<<4
        $params->Person->idcard1 = substr($temp_idcard, 0, 2);
        $params->Person->idcard2 = substr($temp_idcard, 2, 3);
        $params->Person->idcard3 = substr($temp_idcard, 5, 10);
        $params->Person->idcard4 = substr($temp_idcard, 15, 15);
        $params->Person->idcard5 = substr($temp_idcard, 30, 7);
        $params->Person->idcard6 = substr($temp_idcard, 37, 1);
        $params->Person->idcard7 = substr($temp_idcard, 38, 7);
        $params->Person->idcard8 = substr($temp_idcard, 45, 3);
        $params->Person->idcard9 = substr($temp_idcard, 48, 11);
        $params->Person->idcard10 = substr($temp_idcard, 59, 1);

        $temp_drivinglicence = '';
        if ($cascade && $_SESSION['ID_CHECK_TYPE'] === 'drivers_license') {
            $temp_drivinglicence = $_SESSION['ID_CHECK_NUMBER'];
        }
        // E.g.: STONE704116R99FG
        $params->Person->drivinglicence1 = substr($temp_drivinglicence, 0, 5);
        $params->Person->drivinglicence2 = substr($temp_drivinglicence, 5, 6);
        $params->Person->drivinglicence3 = substr($temp_drivinglicence, 11, 5);

        $params->Person->cardnumber = '';
        $params->Person->cardtype = '';

        $params->Person->cardavs->CardType = '';
        $params->Person->cardavs->CardHolder = '';
        $params->Person->cardavs->CardNumber = '';
        $params->Person->cardavs->CardStart = '';
        $params->Person->cardavs->CardExpire = '';
        $params->Person->cardavs->CV2 = '';
        $params->Person->cardavs->IssueNumber = '';
        $params->Person->cardavs->CardAddress->Address1 = '';
        $params->Person->cardavs->CardAddress->Address2 = '';
        $params->Person->cardavs->CardAddress->Address3 = '';
        $params->Person->cardavs->CardAddress->Address4 = '';
        $params->Person->cardavs->CardAddress->Address5 = '';
        $params->Person->cardavs->CardAddress->Postcode = '';
        $params->Person->cardavs->CardAddress->DPS = '';

        $params->Person->ni = '';
        $params->Person->nhs = '';

        $params->Person->bforename = '';
        $params->Person->bmiddle = '';
        $params->Person->bsurname = '';
        $params->Person->maiden = '';
        $params->Person->bdistrict = '';
        $params->Person->bcertificate = '';

        $params->Person->mpannumber1 = '';
        $params->Person->mpannumber2 = '';
        $params->Person->mpannumber3 = '';
        $params->Person->mpannumber4 = '';

        $params->Person->sortcode = '';
        $params->Person->accountnumber = '';

        $params->Person->msubjectforename = '';
        $params->Person->msubjectsurname = '';
        $params->Person->mpartnerforename = '';
        $params->Person->mpartnersurname = '';
        $params->Person->mdate = '';
        $params->Person->mdistrict = '';
        $params->Person->mcertificate = '';

        $params->Person->pollnumber = '';

        $params->Person->email = '';
        $params->Person->email2 = '';

        $params->Person->docfront = '';
        $params->Person->docback = '';
        $params->Person->docsize = '';

        $params->Person->landline1 = '';
        $params->Person->landline2 = '';
        $params->Person->mobile1 = '';
        $params->Person->mobile2 = '';
        $params->Person->otplandline1 = '';
        $params->Person->otplandline2 = '';
        $params->Person->otpmobile1 = '';
        $params->Person->otpmobile2 = '';

        // Enable minimum services required by AML
        $params->Services->address = 1;
        $params->Services->deathscreen = 0;
        $params->Services->dob = 1;
        $params->Services->sanction = 0;
        $params->Services->insolvency = 0;
        $params->Services->crediva = 0;
        $params->Services->ccj = 0;

        // Disable non-required services
        $params->Services->passport = $cascade && $_SESSION['ID_CHECK_TYPE'] === 'passport';
        $params->Services->driving = $cascade && $_SESSION['ID_CHECK_TYPE'] === 'drivers_license';
        $params->Services->birth = 0;
        $params->Services->smartlink = 0;
        $params->Services->ni = 0;
        $params->Services->nhs = 0;
        $params->Services->cardavs = 0;
        $params->Services->cardnumber = 0;
        $params->Services->bankaccountvalidation = 0;
        $params->Services->bankaccountverification = 0;
        $params->Services->mpan = 0;
        $params->Services->bankmatch = 0;
        $params->Services->creditactive = 0;
        $params->Services->travelvisa = 0;
        $params->Services->idcard = $cascade && $_SESSION['ID_CHECK_TYPE'] === 'id_card';
        $params->Services->bankmatchlive = 0;
        $params->Services->companydirector = 0;
        $params->Services->searchactivity = 0;
        $params->Services->noticeofcorrection = 0;
        $params->Services->prs = 0;
        $params->Services->marriage = 0;

        $params->Services->pollnumber = 0;
        $params->Services->onlineprofile = 0;
        $params->Services->age = 0;
        $params->Services->docauth = 0;
        $params->Services->onetimepassword = 0;

        $results = $client->IDUProcess($params);

        $params->Person->drivinglicence1 = self::anonymiseSensitiveData($params->Person->drivinglicence1);
        $params->Person->drivinglicence2 = self::anonymiseSensitiveData($params->Person->drivinglicence2);
        $params->Person->drivinglicence3 = self::anonymiseSensitiveData($params->Person->drivinglicence3);

        $params->Person->passport1 = self::anonymiseSensitiveData($params->Person->passport1);
        $params->Person->passport2 = self::anonymiseSensitiveData($params->Person->passport2);
        $params->Person->passport3 = self::anonymiseSensitiveData($params->Person->passport3);
        $params->Person->passport4 = self::anonymiseSensitiveData($params->Person->passport4);
        $params->Person->passport5 = self::anonymiseSensitiveData($params->Person->passport5);
        $params->Person->passport6 = self::anonymiseSensitiveData($params->Person->passport6);
        $params->Person->passport7 = self::anonymiseSensitiveData($params->Person->passport7);
        $params->Person->passport8 = self::anonymiseSensitiveData($params->Person->passport8);

        $params->Person->idcard1 = self::anonymiseSensitiveData($params->Person->idcard1);
        $params->Person->idcard2 = self::anonymiseSensitiveData($params->Person->idcard2);
        $params->Person->idcard3 = self::anonymiseSensitiveData($params->Person->idcard3);
        $params->Person->idcard4 = self::anonymiseSensitiveData($params->Person->idcard4);
        $params->Person->idcard5 = self::anonymiseSensitiveData($params->Person->idcard5);
        $params->Person->idcard6 = self::anonymiseSensitiveData($params->Person->idcard6);
        $params->Person->idcard7 = self::anonymiseSensitiveData($params->Person->idcard7);
        $params->Person->idcard8 = self::anonymiseSensitiveData($params->Person->idcard8);
        $params->Person->idcard9 = self::anonymiseSensitiveData($params->Person->idcard9);
        $params->Person->idcard10 = self::anonymiseSensitiveData($params->Person->idcard10);

        // Request failed completely
        if ($results->Summary->Status != true) {
            IdCheckLog::query()->create([
                'order_id' => $order->id,
                'member_id' => $order->member,
                'request' => json_encode($params),
                'response' => json_encode($results),
                'raw_response' => json_encode($results),
                'endpoint' => $endpoint,
            ]);

            return self::failedDueToLogin($results) ? self::ID_CHECK_PASSED : self::ID_CHECK_FAILED;

            exit();
        }

        // If cascade is in place (primary attempt failed)
        if ($cascade) {
            // If we are doing divers license check
            if ($cascade && $_SESSION['ID_CHECK_TYPE'] === 'drivers_license') {
                // Check if ID was confirmed
                if ($results->DrivingLicense->ResultFlag === true) {
                    $status = 'success';
                    $return = self::ID_CHECK_PASSED;
                } else {
                    $status = 'fail';
                    $return = self::ID_CHECK_FAILED_CASCADE;
                }
            } elseif ($cascade && $_SESSION['ID_CHECK_TYPE'] === 'passport') {
                // Check if ID was confirmed
                if ($results->Passport->MRZValid === true) {
                    $status = 'success';
                    $return = self::ID_CHECK_PASSED;
                } else {
                    $status = 'fail';
                    $return = self::ID_CHECK_FAILED_CASCADE;
                }
            } else {
                // Check if ID was confirmed
                if ($results->IDCard->MRZValid === true) {
                    $status = 'success';
                    $return = self::ID_CHECK_PASSED;
                } else {
                    $status = 'fail';
                    $return = self::ID_CHECK_FAILED_CASCADE;
                }
            }
        } elseif ($results->Summary->ResultText === 'PASS') {
            $status = 'success';
            $return = self::ID_CHECK_PASSED;
        } else {
            // No cascade, we check if address was matched
            if (in_array(strtolower($results->Address->MatchType), ['full', 'multiple'])) {
                $status = 'success';
                $return = self::ID_CHECK_PASSED;
            } else {
                $status = 'fail';
                $return = self::ID_CHECK_FAILED;
            }
        }

        // Save the API call in log
        IdCheckLog::query()->create([
            'order_id' => $order->id,
            'member_id' => $order->member,
            'request' => json_encode($params),
            'response' => $status,
            'raw_response' => json_encode($results),
            'endpoint' => $endpoint,
        ]);

        // return the result value
        return $return;
    }

    /**
     * Check if the failure happened due to login error
     * This does a bypass of the ID restriction
     *
     * @param $response
     * @return bool
     */
    private static function failedDueToLogin($response)
    {
        return collect($response->Summary->Errors)
            ->where('Service', 'Login')
            ->count() > 0;
    }

    private static function anonymiseSensitiveData(string $sensitive_data)
    {
        $anonymised_data = $sensitive_data;
        $anonymised_data = preg_replace('/[0-9]/', '0', $anonymised_data);
        $anonymised_data = preg_replace('/[a-zA-Z]/', 'x', $anonymised_data);

        return $anonymised_data;
    }
}
