<?php
/**
 * For the full copyright and license information, please view the
 * docs/licenses/LICENSE.txt file that was distributed with this source code.
 */

namespace Tests\Integration\Behaviour\Features\Context;

use CartRule;
use Configuration;
use Context;
use Country;
use Customer;
use Exception;
use PrestaShop\PrestaShop\Adapter\ServiceLocator;
use PrestaShop\PrestaShop\Adapter\Validate;
use PrestaShop\PrestaShop\Core\Crypto\Hashing;
use RuntimeException;

class CustomerFeatureContext extends AbstractPrestaShopFeatureContext
{
    use CartAwareTrait;

    /**
     * @var Customer[]
     */
    protected $customers = [];

    /**
     * @Given /^there is a customer named "(.+)" whose email is "(.+)"$/
     */
    public function createCustomer($customerName, $customerEmail)
    {
        /** @var Hashing $crypto */
        $crypto = ServiceLocator::get(Hashing::class);

        $customer = new Customer();
        $customer->firstname = 'fake';
        $customer->lastname = 'fake';
        $customer->passwd = $crypto->hash('Correct Horse Battery Staple');
        $customer->email = $customerEmail;
        $customer->id_shop = Context::getContext()->shop->id;
        $customer->add();
        $this->customers[$customerName] = $customer;
        SharedStorage::getStorage()->set($customerName, $customer->id);
    }

    /**
     * @Given /^there is a guest customer named "(.+)" whose email is "(.+)"$/
     */
    public function createGuestCustomer($customerName, $customerEmail)
    {
        /** @var Hashing $crypto */
        $crypto = ServiceLocator::get(Hashing::class);

        $customer = new Customer();
        $customer->firstname = 'fake';
        $customer->lastname = 'fake';
        $customer->passwd = $crypto->hash('Correct Horse Battery Staple');
        $customer->email = $customerEmail;
        $customer->is_guest = true;
        $customer->id_shop = Context::getContext()->shop->id;
        $customer->add();
        $this->customers[$customerName] = $customer;
        SharedStorage::getStorage()->set($customerName, $customer->id);
    }

    /**
     * @Given /^customer "(.+)" belongs to group "(.+)"$/
     */
    public function assignCustomerToGroup(string $customerReference, string $groupReference): void
    {
        $customerId = SharedStorage::getStorage()->get($customerReference);
        $groupId = SharedStorage::getStorage()->get($groupReference);

        $customer = new Customer($customerId);
        // Add the group to the customer's groups
        $customer->addGroups([$groupId]);
    }

    /**
     * @Given there is customer :reference with email :customerEmail
     */
    public function customerExists($reference, $customerEmail)
    {
        $data = Customer::getCustomersByEmail($customerEmail);
        if (isset($data[0]['id_customer'])) {
            $customer = new Customer($data[0]['id_customer']);
        }

        if (empty($customer) || !Validate::isLoadedObject($customer)) {
            throw new Exception(sprintf('Customer with email "%s" does not exist.', $customerEmail));
        }

        SharedStorage::getStorage()->set($reference, (int) $customer->id);
    }

    /**
     * @Given customer :reference has address in :isoCode country
     */
    public function customerHasAddressInCountry($reference, $isoCode)
    {
        $customer = $this->getCustomerByReference($reference);
        $customerAddresses = $customer->getAddresses((int) Configuration::get('PS_LANG_DEFAULT'));

        foreach ($customerAddresses as $address) {
            $country = new Country($address['id_country']);

            if ($country->iso_code === $isoCode) {
                return;
            }
        }

        throw new RuntimeException(sprintf('Customer does not have address in "%s" country', $isoCode));
    }

    /**
     * @Given /^the customer "(.+)" has SIRET "(.+)"$/
     */
    public function customerHasSIRET(string $reference, string $siret): void
    {
        $customer = $this->getCustomerByReference($reference);
        $customer->siret = $siret;
        $customer->save();
    }

    /**
     * @Given /^the customer "(.+)" has APE "(.+)"$/
     */
    public function customerHasAPE(string $reference, string $ape): void
    {
        $customer = $this->getCustomerByReference($reference);
        $customer->ape = $ape;
        $customer->save();
    }

    /**
     * @When /^I am logged in as "(.+)"$/
     */
    public function setCurrentCustomer($customerName)
    {
        $this->checkCustomerWithNameExists($customerName);
        Context::getContext()->updateCustomer($this->customers[$customerName]);
    }

    /**
     * @Given private note is not set about customer :reference
     */
    public function assertPrivateNoteIsNotSetAboutCustomer($reference)
    {
        $customer = $this->getCustomerByReference($reference);

        if ($customer->note) {
            throw new RuntimeException(sprintf('It was expected that customer "%s" should not have private note.', $reference));
        }
    }

    /**
     * @Then customer :reference private note should be :privateNote
     */
    public function assertPrivateNoteAboutCustomer($reference, $privateNote)
    {
        $customer = $this->getCustomerByReference($reference);

        if ($customer->note !== $privateNote) {
            throw new RuntimeException(sprintf('It was expected that customer "%s" private note should be "%s", but actually is "%s".', $reference, $privateNote, $customer->note));
        }
    }

    /**
     * @Then customer :reference last voucher is :voucherAmount
     */
    public function checkCustomerHasVoucher(string $reference, float $voucherAmount)
    {
        $customer = $this->getCustomerByReference($reference);
        $cartRules = CartRule::getCustomerCartRules((int) Configuration::get('PS_LANG_DEFAULT'), $customer->id, true, false);
        if (empty($cartRules)) {
            throw new RuntimeException('Cannot find any cart rules for customer');
        }

        $voucher = $cartRules[count($cartRules) - 1];
        if ($voucherAmount !== (float) $voucher['reduction_amount']) {
            throw new RuntimeException(sprintf('Invalid voucher amount, expected %s but got %s instead', $voucherAmount, $voucher['reduction_amount']));
        }
    }

    /**
     * @param string $customerName
     */
    public function checkCustomerWithNameExists(string $customerName): void
    {
        $this->checkFixtureExists($this->customers, 'Customer', $customerName);
    }

    /**
     * @param string $customerName
     *
     * @return Customer
     */
    public function getCustomerWithName(string $customerName): Customer
    {
        return $this->customers[$customerName];
    }

    /**
     * @AfterScenario
     */
    public function cleanCustomerFixtures()
    {
        foreach ($this->customers as $customer) {
            $customer->delete();
        }
        $this->customers = [];
    }

    /**
     * @param string $customerReference
     *
     * @return Customer
     */
    private function getCustomerByReference(string $customerReference): Customer
    {
        $customerId = SharedStorage::getStorage()->get($customerReference);

        return new Customer($customerId);
    }
}
