<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

declare(strict_types=1);

namespace SimplyIn\Ajax\Service;

use CompareProduct;
use Context;
use Customer;
use Hook;
use Tools;

if (!defined('_PS_VERSION_')) {
    exit;
}

class CustomerService
{
    public function __construct(private readonly Context $context)
    {
    }

    /**
     * @param array<string, mixed> $responseData payload returned by userDataCommerce
     *
     * @return array{is_guest: bool, firstname?: string, lastname?: string, challenge_available?: bool}
     */
    public function loginOrCreateByEmail(string $email, array $responseData): array
    {
        $data = $this->extractData($responseData);
        $customer = new Customer();
        $customer->getByEmail($data['email'] ?? $email);

        if ((int) $customer->id > 0) {
            if ($customer->is_guest) {
                $customer->firstname = (string) ($data['name'] ?? '');
                $customer->lastname = (string) ($data['surname'] ?? '');
                $customer->update();
            } elseif (!$this->isAlreadyLoggedInAsCustomer((int) $customer->id)) {
                // Logging into a registered shop account requires the backend to confirm the shopper
                // owns the email (shopLoginAllowed === verified email); a missing flag is treated as
                // not allowed and falls back to a guest checkout instead. This is an anti-takeover
                // guard: without it, a shopper who only verifies their own phone number could gain
                // login access to whichever registered account matches a typed email, without ever
                // proving they control that email/account. Skipped entirely (see
                // isAlreadyLoggedInAsCustomer) when the browser is already logged into THIS exact
                // account via the shop's own password — nothing was typed, so there is no account to
                // take over. Safe regardless of which caller resolved $customer (a typed email is
                // never enough on its own): the exemption only engages when the CURRENT native
                // session's id equals this exact target id, so a browser logged in as one account can
                // never borrow it to reach a different one via the email this method resolves.
                if (true !== ($responseData['shopLoginAllowed'] ?? null)) {
                    // When the backend explicitly reports the email as unverified, offer the email-OTP
                    // challenge so the real owner can verify and log in instead of being stranded (a
                    // registered PS email cannot buy as a guest). A missing flag stays a plain guest.
                    return [
                        'is_guest' => true,
                        'firstname' => (string) ($data['name'] ?? ''),
                        'lastname' => (string) ($data['surname'] ?? ''),
                        'challenge_available' => false === ($responseData['shopLoginAllowed'] ?? null),
                    ];
                }
                Hook::exec('actionBeforeAuthentication');
                if (class_exists('CompareProduct') && !isset($this->context->cookie->id_compare)) {
                    $this->context->cookie->id_compare = CompareProduct::getIdCompareByIdCustomer((int) $customer->id);
                }
            }

            $this->context->cookie->id_customer = (int) $customer->id;
            $this->context->updateCustomer($customer);

            return $customer->is_guest
                ? ['is_guest' => true, 'firstname' => (string) $customer->firstname, 'lastname' => (string) $customer->lastname]
                : ['is_guest' => false];
        }

        $customer->email = $email;
        $customer->passwd = Tools::hash(Tools::passwdGen());
        $customer->is_guest = true;
        $customer->firstname = (string) ($data['name'] ?? '');
        $customer->lastname = (string) ($data['surname'] ?? '');
        $customer->add();
        $this->context->cookie->id_customer = (int) $customer->id;
        $this->context->updateCustomer($customer);

        return [
            'is_guest' => true,
            'firstname' => (string) $customer->firstname,
            'lastname' => (string) $customer->lastname,
        ];
    }

    /**
     * True when the PrestaShop session is already logged in — via the shop's own native login, not
     * ours — as this exact (non-guest) customer. loginOrCreateByEmail() is shared by every
     * checkout-code path (SMS, push notification, email-first challenge), so this has to hold
     * regardless of which one got here: a native, non-guest PrestaShop session already vetted this
     * exact account, which is ownership proof on its own — there is nothing left for the anti-takeover
     * gate below to protect against.
     */
    private function isAlreadyLoggedInAsCustomer(int $customerId): bool
    {
        $current = $this->context->customer;

        return $current !== null
            && $current->isLogged()
            && !$current->isGuest()
            && (int) $current->id === $customerId;
    }

    /**
     * @param array<string, mixed> $responseData
     *
     * @return array<string, mixed>
     */
    private function extractData(array $responseData): array
    {
        if (isset($responseData['data']) && is_array($responseData['data'])) {
            return $responseData['data'];
        }
        if (isset($responseData['userData']) && is_array($responseData['userData'])) {
            return $responseData['userData'];
        }

        return [];
    }
}
