<?php

namespace App\Services\Shopify;

use App\Helpers\MiscHelper;
use App\Models\Address;
use App\Models\Catalog;
use App\Models\Company;
use App\Models\CompanyLocation;
use App\Models\Customer;
use Illuminate\Support\Facades\Log;

/**
 * Service class that holds all customer related Shopify related interaction and business logic
 */
class CustomersService extends BaseService
{
    const SHOPIFY_COMPANY_LOCATION_CONTACT_LIMIT = 50;

    /**
     * Syncs a given customer with Shopify
     *
     * @param Customer $customer
     * @return bool
     */
    public function syncCustomer(Customer $customer)
    {
        Log::info('Syncing Customer ' . $customer->nav_no . ' to Shopify...');

        if (!$customer->company->shopify_gid) {
            Log::warning('Unable to sync customer ' . $customer->nav_no . ' to Shopify: Company not in Shopify yet.');
            return false;
        }

        $customerInput = [
            'email' => $customer->email,
            'firstName' => $customer->first_name,
            'lastName' => $customer->last_name
        ];

        // checks for duplicated existing phone numbers, since shopify doesn't allow duplicates
        $uniquePhoneCustomer = Customer::where('id', '<>', $customer->id)
            ->whereNotNull('shopify_gid')
            ->where('phone', $customer->phone)
            ->first();

        if (!$uniquePhoneCustomer && $customer->phone) {
            $customerInput['phone'] = $customer->phone;
        }

        if (!$customer->shopify_gid) {
            $query = '
                mutation companyContactCreate($companyId: ID!, $input: CompanyContactInput!) {
                    companyContactCreate(companyId: $companyId, input: $input) {
                        companyContact {
                            id
                            customer {
                                id
                            }
                        }
                        userErrors {
                            field
                            message
                        }
                    }
                }
            ';

            $response = $this->api->graph($query, [
                'companyId' => $customer->company->shopify_gid,
                'input' => $customerInput
            ]);

            if (!$this->_checkResponse($response, 'companyContactCreate')) {
                // detects invalid phone number and insists without phone
                if (
                    !empty($response['body']['data']['companyContactCreate']['userErrors'][0]['field'][1])
                    && $response['body']['data']['companyContactCreate']['userErrors'][0]['field'][1] == 'phone'
                ) {
                    unset($customerInput['phone']);
                    $response = $this->api->graph($query, [
                        'companyId' => $customer->company->shopify_gid,
                        'input' => $customerInput
                    ]);

                    if (!$this->_checkResponse($response, 'companyContactCreate')) {
                        return false;
                    }
                } else {
                    return false;
                }
            }

            $responseContact = $response['body']['data']['companyContactCreate']['companyContact'];
            $customer->shopify_gid = $responseContact['customer']['id'];
            $customer->contact_gid = $responseContact['id'];
        } else {
            $query = '
                mutation companyContactUpdate($companyContactId: ID!, $input: CompanyContactInput!) {
                    companyContactUpdate(companyContactId: $companyContactId, input: $input) {
                        userErrors {
                            field
                            message
                        }
                    }
                }
            ';

            $response = $this->api->graph($query, [
                'companyContactId' => $customer->contact_gid,
                'input' => $customerInput
            ]);

            if (!$this->_checkResponse($response, 'companyContactUpdate')) {
                // detects invalid phone number and insists without phone
                if (
                    !empty($response['body']['data']['companyContactUpdate']['userErrors'][0]['field'][1])
                    && $response['body']['data']['companyContactUpdate']['userErrors'][0]['field'][1] == 'phone'
                ) {
                    unset($customerInput['phone']);
                    $response = $this->api->graph($query, [
                        'companyContactId' => $customer->contact_gid,
                        'input' => $customerInput
                    ]);

                    if (!$this->_checkResponse($response, 'companyContactUpdate')) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }

        if ($customer->companyLocations->count() == 0) {
            $location = CompanyLocation::where('company_id', $customer->company_id)
                ->has('customers', '<', self::SHOPIFY_COMPANY_LOCATION_CONTACT_LIMIT)
                ->first();
            if (!$location) {
                // creates new location to provide more contacts for the company
                $location = $this->_createCompanyLocation($customer->company);
            }
            if ($location) {
                $this->_assignContactRole($customer, $location);
            } else {
                Log::warning('Unable to assign contact to company location: Customer ID ' . $customer->id);
            }
        }

        $customer->to_sync = false;
        $customer->save();

        Log::info('Created / Updated shopify customer ' . $customer->nav_no . ': ' . $customer->shopify_gid);
        return true;
    }

    /**
     * Syncs pending customers with shopify
     *
     * @return void
     */
    public function syncCustomers()
    {
        $customers = Customer::where('to_sync', true)->get();

        Log::info('Found ' . $customers->count() . ' customers to sync with Shopify...');

        foreach ($customers as $customer) {
            $this->syncCustomer($customer);
        }
    }

    /**
     * Syncs a given company with Shopify. If creating, also creates the main company contact
     *
     * @param Company $company
     * @return bool
     */
    public function syncCompany(Company $company)
    {
        Log::info('Syncing Company ' . $company->nav_no . ' to Shopify...');

        $companyInput = [
            'externalId' => $company->nav_no,
            'name' => $company->name
        ];
        /*
        $mainContact = $company->mainCustomer;


        $companyMainContactInput = [
            'email' => $mainContact->email,
            'firstName' => $mainContact->first_name,
            'lastName' => $mainContact->last_name,
            'phone' => $mainContact->phone,
            'title' => $mainContact->title
        ];
        */

        $companyLocationInput = $this->_getCompanyLocationInput($company->name, $company->billingAddress);

        $input = null;
        if (!$company->shopify_gid) {
            $mutation = 'companyCreate';
            $query = '
                mutation companyCreate($input: CompanyCreateInput!) {
                    companyCreate(input: $input) {
                        company {
                            id
                            mainContact {
                                id
                                customer {
                                    id
                                }
                            }
                            locations(first:1) {
                                edges {
                                    node {
                                        id
                                    }
                                }
                            }
                            defaultRole {
                                id
                            }
                        }
                        userErrors {
                            field
                            message
                        }
                    }
                }
            ';

            $input = [
                'company' => $companyInput,
                //'companyContact' => $companyMainContactInput,
                'companyLocation' => $companyLocationInput
            ];

            $response = $this->api->graph($query, [
                'input' => $input
            ]);

            if (!$this->_checkResponse($response, $mutation)) {
                return false;
            }

            $responseCompany = $response['body']['data'][$mutation]['company'];
            //$responseCustomer = $responseCompany['mainContact']['customer'];
            $responseLocation = $responseCompany['locations']['edges'][0]['node'];

            $company->shopify_gid = $responseCompany['id'];
            $company->location_gid = $responseLocation['id'];
            $company->default_role_gid = $responseCompany['defaultRole']['id'];

            $this->_createCompanyLocation($company, $responseLocation['id']);

            // $mainContact->shopify_gid = $responseCustomer['id'];
            // $mainContact->contact_gid = $responseCompany['mainContact']['id'];
        } else {
            $mutation = 'companyUpdate';
            $query = '
                mutation companyUpdate($companyId: ID!, $input: CompanyInput!) {
                    companyUpdate(companyId: $companyId, input: $input) {
                        userErrors {
                            field
                            message
                        }
                    }
                }
            ';

            $response = $this->api->graph($query, [
                'companyId' => $company->shopify_gid,
                'input' => $companyInput
            ]);

            if (!$company->locations->count()) {
                $this->_createCompanyLocation($company, $company->location_gid);
            }

            if (!$this->_checkResponse($response, $mutation)) {
                return false;
            }

            /* NO MORE ADRESS SYNC
            if ($company->billingAddress) {
                foreach ($company->locations as $location) {
                    if (!$this->syncCompanyLocationAddress($location)) {
                        Log::warning('Unable to Sync Company Location Address: ID: ' . $location->id);
                    }
                }
            }
            */
        }

        if (config('navision.sales_code') == 'INC') {
            // Sync US Tax exemptions
            foreach ($company->locations as $location) {
                $this->syncCompanyLocationTaxExemptions($location);
            }
        }

        /*
        $mainContact->to_sync = false;
        $mainContact->save();
        */
        $company->to_sync = false;
        $company->save();
        Log::info('Created / Updated shopify company ' . $company->nav_no . ': ' . $company->shopify_gid);

        if (!$this->syncCompanyCatalogs($company)) {
            Log::warning('Unable to sync company catalogs ' . $company->nav_no . ': ' . $company->shopify_gid);
        }

        return true;
    }

    /**
     * Syncs pending companies with shopify
     *
     * @return void
     */
    public function syncCompanies()
    {
        $companies = Company::where('to_sync', true)->get();

        Log::info('Found ' . $companies->count() . ' companies to sync with Shopify...');

        foreach ($companies as $company) {
            $this->syncCompany($company);
        }
    }

    /**
     * Syncs company catalogs with shopify
     *
     * @param Company $company
     * @return bool
     */
    public function syncCompanyCatalogs(Company $company)
    {
        $success = true;
        foreach($company->locations as $location) {
            $query = '
                query getCompanyLocationById($id: ID!) {
                    companyLocation(id: $id) {
                        catalogs(first:20) {
                            edges {
                                node {
                                    id
                                    title
                                }
                            }
                        }
                    }
                }
            ';

            $response = $this->api->graph($query, [
                'id' => $location->shopify_gid
            ]);

            $currentCatalogGids = [];
            foreach ($response['body']['data']['companyLocation']['catalogs']['edges'] as $node) {
                //if (str_starts_with($node['node']['title'], 'NC-EXCLUSIVE')) {
                //    continue;
                //}
                $currentCatalogGids[] = $node['node']['id'];
            }

            $catalogGids = Catalog::whereIn('customer_discount_group', ['*', $company->discount_group])->get()->pluck('shopify_gid')->toArray();
            if ($company->exclusive_catalog_gid) {
                $catalogGids[] = $company->exclusive_catalog_gid;
            }

            $catalogsToAdd = array_diff($catalogGids, $currentCatalogGids);
            $catalogsToRemove = array_diff($currentCatalogGids, $catalogGids);

            if (empty($catalogsToAdd) && empty($catalogsToRemove)) {
                continue;
            }

            foreach ($catalogsToAdd as $catalogGid) {
                $success = $success && $this->updateCatalogContext($catalogGid, [$location->shopify_gid], []);
            }
            foreach ($catalogsToRemove as $catalogGid) {
                $success = $success && $this->updateCatalogContext($catalogGid, [], [$location->shopify_gid]);
            }
        }
        return $success;
    }

    /**
     * Syncs a company's location address with shopify
     *
     * @param CompanyLocation $companyLocation
     * @return bool
     */
    public function syncCompanyLocationAddress(CompanyLocation $companyLocation)
    {
        $billingAddress = $companyLocation->billingAddress;
        $addressInput = [
            'address1' => $billingAddress->address1,
            'address2' => $billingAddress->address2,
            'city' => $billingAddress->city,
            'countryCode' => $billingAddress->country_code,
            'zip' => $billingAddress->zip
        ];

        if (!empty($billingAddress->phone)) {
            $addressInput['phone'] = $billingAddress->phone;
        }

        $query = '
            mutation companyLocationAssignAddress($address: CompanyAddressInput!, $addressTypes: [CompanyAddressType!]!, $locationId: ID!) {
                companyLocationAssignAddress(address: $address, addressTypes: $addressTypes, locationId: $locationId) {
                    addresses {
                        id
                    }
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'address' => $addressInput,
            'addressTypes' => ['SHIPPING', 'BILLING'],
            'locationId' => $companyLocation->shopify_gid
        ]);

        if (!$this->_checkResponse($response, 'companyLocationAssignAddress')) {
            return false;
        }
        return true;
    }

    /**
     * Syncs a company's location tax exemptions
     *
     * @param CompanyLocation $companyLocation
     * @return bool
     */
    public function syncCompanyLocationTaxExemptions(CompanyLocation $companyLocation)
    {
        $taxExemptions = $companyLocation->company->is_tax_exempt ? array_values(config('shopify-app.us_tax_exemptions')) : [];

        $query = '
            mutation companyLocationAssignTaxExemptions($companyLocationId: ID!, $taxExemptions: [TaxExemption!]!) {
                companyLocationAssignTaxExemptions(companyLocationId: $companyLocationId, taxExemptions: $taxExemptions) {
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'companyLocationId' => $companyLocation->shopify_gid,
            'taxExemptions' => $taxExemptions
        ]);

        if (!$this->_checkResponse($response, 'companyLocationAssignTaxExemptions')) {
            return false;
        }
        return true;
    }

    /**
     * Updates a shopify catalog, adding or removing the provided company ids
     *
     * @param string $catalogGid
     * @param array $companyLocationsGidsToAdd
     * @param array $companyLocationsGidsToRemove
     * @return bool
     */
    public function updateCatalogContext(string $catalogGid, array $companyLocationsGidsToAdd, array $companyLocationsGidsToRemove)
    {
        $query = '
            mutation catalogContextUpdate($catalogId: ID!, $contextsToAdd: CatalogContextInput, $contextsToRemove: CatalogContextInput) {
                catalogContextUpdate(catalogId: $catalogId, contextsToAdd: $contextsToAdd, contextsToRemove: $contextsToRemove) {
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'catalogId' => $catalogGid,
            'contextsToAdd' => [
                'companyLocationIds' => $companyLocationsGidsToAdd
            ],
            'contextsToRemove' => [
                'companyLocationIds' => $companyLocationsGidsToRemove
            ],
        ]);

        if (!$this->_checkResponse($response, 'catalogContextUpdate')) {
            return false;
        }
        return true;
    }

    /**
     * Deletes all adresses for companies in Shopify
     *
     * @return void
     */
    public function clearCompaniesAddresses()
    {
        $companies = Company::where(function ($query) {
                $query->whereNotNull('billing_address_id')
                    ->orWhereNotNull('shipping_address_id');
            })
            ->with('locations')
            ->get();

        foreach ($companies as $company) {
            $this->clearCompanyAddresses($company);
        }
    }

    /**
     * Deletes adresses for a company in Shopify
     *
     * @return void
     */
    public function clearCompanyAddresses(Company $company)
    {
        foreach($company->locations as $location) {
            if (!$location->shopify_gid) {
                Log::info('Unable to clear company location addresses: No location gid for id ' . $location->id);
                continue;
            }

            $query = '
                query getCompanyLocationById($id: ID!) {
                    companyLocation(id: $id) {
                        id
                        billingAddress {
                            id
                        }
                        shippingAddress {
                            id
                        }
                    }
                }
            ';

            $response = $this->api->graph($query, [
                'id' => $location->shopify_gid
            ]);

            if (empty($response['body']['data']['companyLocation'])) {
                Log::error('Error getting company\'s location: ' . json_encode($response));
                continue;
            }

            $billingAddressGid = null;
            $shippingAddressGid = null;

            if (!empty($response['body']['data']['companyLocation']['billingAddress']['id'])) {
                $billingAddressGid = $response['body']['data']['companyLocation']['billingAddress']['id'];
                $this->deleteCompanyAddress($billingAddressGid);
            }

            if (!empty($response['body']['data']['companyLocation']['shippingAddress']['id'])) {
                $shippingAddressGid = $response['body']['data']['companyLocation']['shippingAddress']['id'];
                if ($shippingAddressGid !== $billingAddressGid) {
                    $this->deleteCompanyAddress($shippingAddressGid);
                }
            }

            if (!$billingAddressGid && !$shippingAddressGid) {
                Log::info('Company Location has no addresses: ' . $location->shopify_gid);
            }
        }
    }

    /**
     * Deletes a shopify company address
     *
     * @param string $adressGid
     * @return boolean
     */
    protected function deleteCompanyAddress(string $adressGid): bool
    {
        $query = '
            mutation companyAddressDelete($addressId: ID!) {
                companyAddressDelete(addressId: $addressId) {
                    deletedAddressId
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'addressId' => $adressGid
        ]);

        if (!$this->_checkResponse($response, 'companyAddressDelete')) {
            return false;
        }

        Log::info('Company Address Deleted: ' . $adressGid);

        return true;
    }

    /**
     * Assigns the default company role to the given customer contact
     *
     * @param Customer $customer
     * @return bool
     */
    protected function _assignContactRole(Customer $customer, CompanyLocation $location)
    {
        // check if not already assigned
        if (in_array($customer->contact_gid, $this->getCompanyLocationContactGids($location->shopify_gid))) {
            Log::info('Company contact already assigned to company location. Customer Id ' . $customer->id);
            $location->customers()->attach($customer->id);
            return true;
        }

        $query = '
            mutation companyContactAssignRole($companyContactId: ID!, $companyContactRoleId: ID!, $companyLocationId: ID!) {
                companyContactAssignRole(companyContactId: $companyContactId, companyContactRoleId: $companyContactRoleId, companyLocationId: $companyLocationId) {
                    companyContactRoleAssignment {
                        id
                    }
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'companyContactId' => $customer->contact_gid,
            'companyContactRoleId' => $customer->company->default_role_gid,
            'companyLocationId' => $location->shopify_gid
        ]);

        if (!$this->_checkResponse($response, 'companyContactAssignRole')) {
            return false;
        }

        $location->customers()->attach($customer->id);
        Log::info('Company contact assigned to company location. Customer Id ' . $customer->id);

        return true;
    }

    /**
     * Prepares Company Location input data for Shopify request
     *
     * @param string $name
     * @param Address|null $billingAddress
     * @return array
     */
    protected function _getCompanyLocationInput(string $name, Address $billingAddress = null)
    {
        $companyLocationInput = [
            'buyerExperienceConfiguration' => [
                'checkoutToDraft' => false,
                'editableShippingAddress' => true,
            ],
            'name' => $name
        ];

        /* NO MORE ADRESS SYNC
        if ($billingAddress) {
            $companyLocationInput['shippingAddress'] = [
                'address1' => $billingAddress->address1,
                'address2' => $billingAddress->address2,
                'city' => $billingAddress->city,
                'countryCode' => $billingAddress->country_code,
                'phone' => $billingAddress->phone ?: null,
                'zip' => MiscHelper::checkZipCode($billingAddress->zip, $billingAddress->country_code) ? $billingAddress->zip : null
            ];
            $companyLocationInput['billingSameAsShipping'] = true;
        }
        */

        return $companyLocationInput;
    }

    /**
     * Creates, if not exists yet, a company location for a company and a local Company Location
     *
     * @param Company $company
     * @param string|null $locationGid
     * @return CompanyLocation
     */
    protected function _createCompanyLocation(Company $company, string $locationGid = null)
    {
        $newLocation = false;
        if (!$locationGid) {
            // Creates a new company location in shopify
            $companyLocationInput = $this->_getCompanyLocationInput($company->name, $company->billingAddress);
            $query = '
                mutation companyLocationCreate($companyId: ID!, $input: CompanyLocationInput!) {
                    companyLocationCreate(companyId: $companyId, input: $input) {
                        companyLocation {
                            id
                        }
                        userErrors {
                            field
                            message
                        }
                    }
                }
            ';

            $response = $this->api->graph($query, [
                'companyId' => $company->shopify_gid,
                'input' => $companyLocationInput
            ]);

            if (!$this->_checkResponse($response, 'companyLocationCreate')) {
                return false;
            }

            $locationGid = $response['body']['data']['companyLocationCreate']['companyLocation']['id'];
            $newLocation = true;
        }

        $companyLocation = new CompanyLocation();
        $companyLocation->company_id = $company->id;
        $companyLocation->shopify_gid = $locationGid;
        $companyLocation->name = $company->name;
        $companyLocation->billing_address_id = $company->billing_address_id;

        $companyLocation->save();
        $company->load('locations');

        if ($newLocation) {
            $this->syncCompanyCatalogs($company);
        }

        return $companyLocation;
    }

    /**
     * Gets all contacts GIDs for a given company Location
     *
     * @param string $locationGid
     * @return array - Contact Gids
     */
    public function getCompanyLocationContactGids(string $locationGid)
    {
        $query = '
            query getCompanyLocationContactIds($id: ID!) {
                companyLocation(id: $id) {
                    roleAssignments(first: 50) {
                        edges {
                            node {
                                id
                                companyContact {
                                    id
                                }
                            }
                        }
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'id' => $locationGid
        ]);

        return array_map(function ($node) {
            return $node['node']['companyContact']['id'];
        }, $response['body']['data']['companyLocation']['roleAssignments']['edges']->toArray());
    }

    /**
     * Gets all contacts GIDs and customer info for a given company Location
     *
     * @param string $locationGid
     * @return array - Contact Gids
     */
    public function getCompanyLocationContactGidsExtra(string $locationGid)
    {
        $query = '
            query getCompanyLocationContactIds($id: ID!) {
                companyLocation(id: $id) {
                    roleAssignments(first: 50) {
                        edges {
                            node {
                                id
                                companyContact {
                                    id
                                    customer {
                                        id
                                        email
                                    }
                                }
                            }
                        }
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'id' => $locationGid
        ]);

        return array_map(function ($node) {
            return [
                'contactGid' => $node['node']['companyContact']['id'],
                'customerGid' => $node['node']['companyContact']['customer']['id'],
                'customerEmail' => $node['node']['companyContact']['customer']['email']
            ];
        }, $response['body']['data']['companyLocation']['roleAssignments']['edges']->toArray());
    }
}
