<?php
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved
 */

namespace Facebook\BusinessExtension\Helper;

use Facebook\BusinessExtension\Logger\Logger;
use Facebook\BusinessExtension\Model\ConfigFactory;
use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\Module\ModuleListInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\UrlInterface;

use FacebookAds\Object\ServerSide\AdsPixelSettings;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;

class FBEHelper extends AbstractHelper
{
    const MAIN_WEBSITE_STORE = 'Main Website Store';
    const MAIN_STORE = 'Main Store';
    const MAIN_WEBSITE = 'Main Website';

    const FB_GRAPH_BASE_URL = "https://graph.facebook.com/";

    const DELETE_SUCCESS_MESSAGE = "You have successfully deleted Facebook Business Extension.
    The pixel installed on your website is now deleted.";

    const DELETE_FAILURE_MESSAGE = "There was a problem deleting the connection.
      Please try again.";

    const CURRENT_API_VERSION = "v9.0";

    const MODULE_NAME = "Facebook_BusinessExtension";

    /**
     * @var \Magento\Framework\ObjectManagerInterface
     */
    protected $objectManager;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var ConfigFactory
     */
    protected $configFactory;

    /**
     * @var Logger
     */
    protected $logger;

    /**
     * @var DirectoryList
     */
    protected $directoryList;

    /**
     * @var Curl
     */
    protected $curl;

    /**
     * @var ResourceConnection
     */
    protected $resourceConnection;

    /**
     * @var ModuleListInterface
     */
    protected $moduleList;

    /**
     * FBEHelper constructor
     *
     * @param Context $context
     * @param ObjectManagerInterface $objectManager
     * @param ConfigFactory $configFactory
     * @param Logger $logger
     * @param DirectoryList $directorylist
     * @param StoreManagerInterface $storeManager
     * @param Curl $curl
     * @param ResourceConnection $resourceConnection
     * @param ModuleListInterface $moduleList
     */
    public function __construct(
        Context $context,
        ObjectManagerInterface $objectManager,
        ConfigFactory $configFactory,
        Logger $logger,
        DirectoryList $directorylist,
        StoreManagerInterface $storeManager,
        Curl $curl,
        ResourceConnection $resourceConnection,
        ModuleListInterface $moduleList
    ) {
        parent::__construct($context);
        $this->objectManager = $objectManager;
        $this->storeManager = $storeManager;
        $this->configFactory = $configFactory;
        $this->logger = $logger;
        $this->directoryList = $directorylist;
        $this->curl = $curl;
        $this->resourceConnection = $resourceConnection;
        $this->moduleList = $moduleList;
    }

    public function getPixelID()
    {
        return $this->getConfigValue('fbpixel/id');
    }

    public function getAccessToken()
    {
        return $this->getConfigValue('fbaccess/token');
    }

    /**
     * @return mixed
     */
    public function getMagentoVersion()
    {
        return $this->objectManager->get(ProductMetadataInterface::class)->getVersion();
    }

    /**
     * @return mixed
     */
    public function getPluginVersion()
    {
        return $this->moduleList->getOne(self::MODULE_NAME)['setup_version'];
    }

    /**
     * @return string
     */
    public function getSource()
    {
        return 'magento2';
    }

    /**
     * @return string
     */
    public function getPartnerAgent($with_magento_version = false)
    {
        return sprintf(
            '%s-%s-%s',
            $this->getSource(),
            $with_magento_version ? $this->getMagentoVersion() : '0.0.0',
            $this->getPluginVersion()
        );
    }

    /**
     * @param $partialURL
     * @return mixed
     */
    public function getUrl($partialURL)
    {
        $urlInterface = $this->getObject(\Magento\Backend\Model\UrlInterface::class);
        return $urlInterface->getUrl($partialURL);
    }

    /**
     * @return mixed
     */
    public function getBaseUrlMedia()
    {
        return $this->getStore()->getBaseUrl(
            UrlInterface::URL_TYPE_MEDIA,
            $this->maybeUseHTTPS()
        );
    }

    /**
     * @return bool
     */
    private function maybeUseHTTPS()
    {
        return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
    }

    /**
     * @param $fullClassName
     * @param array $arguments
     * @return mixed
     */
    public function createObject($fullClassName, array $arguments = [])
    {
        return $this->objectManager->create($fullClassName, $arguments);
    }

    /**
     * @param $fullClassName
     * @return mixed
     */
    public function getObject($fullClassName)
    {
        return $this->objectManager->get($fullClassName);
    }

    /**
     * @param $id
     * @return bool
     */
    public static function isValidFBID($id)
    {
        return preg_match("/^\d{1,20}$/", $id) === 1;
    }

    /**
     * @return StoreInterface
     */
    public function getStore()
    {
        return $this->storeManager->getDefaultStoreView();
    }

    /**
     * @return mixed
     */
    public function getBaseUrl()
    {
        // Use this function to get a base url respect to host protocol
        return $this->getStore()->getBaseUrl(
            UrlInterface::URL_TYPE_WEB,
            $this->maybeUseHTTPS()
        );
    }

    /**
     * @param $configKey
     * @param $configValue
     */
    public function saveConfig($configKey, $configValue)
    {
        try {
            $configRow = $this->configFactory->create()->load($configKey);
            if ($configRow->getData('config_key')) {
                $configRow->setData('config_value', $configValue);
                $configRow->setData('update_time', time());
            } else {
                $t = time();
                $configRow->setData('config_key', $configKey);
                $configRow->setData('config_value', $configValue);
                $configRow->setData('creation_time', $t);
                $configRow->setData('update_time', $t);
            }
            $configRow->save();
        } catch (\Exception $e) {
            $this->logException($e);
        }
    }

    /**
     * @param $configKey
     */
    public function deleteConfig($configKey)
    {
        try {
            $configRow = $this->configFactory->create()->load($configKey);
            $configRow->delete();
        } catch (\Exception $e) {
            $this->logException($e);
        }
    }

    /**
     * @param $configKey
     * @return mixed|null
     */
    public function getConfigValue($configKey)
    {
        try {
            $configRow = $this->configFactory->create()->load($configKey);
        } catch (\Exception $e) {
            $this->logException($e);
            return null;
        }
        return $configRow ? $configRow->getConfigValue() : null;
    }

    /**
     * @param $requestParams
     * @param null $accessToken
     * @return string|null
     */
    public function makeHttpRequest($requestParams, $accessToken = null)
    {
        $response = null;
        if ($accessToken == null) {
            $accessToken = $this->getConfigValue('fbaccess/token');
        }
        try {
            $url = $this->getCatalogBatchAPI($accessToken);
            $params = [
                'access_token' => $accessToken,
                'requests' => json_encode($requestParams),
                'item_type' => 'PRODUCT_ITEM',
            ];
            $this->curl->post($url, $params);
            $response = $this->curl->getBody();
        } catch (\Exception $e) {
            $this->logException($e);
        }
        return $response;
    }

    /**
     * @return mixed|string|null
     */
    public function getFBEExternalBusinessId()
    {
        $stored_external_id = $this->getConfigValue('fbe/external/id');
        if ($stored_external_id) {
            return $stored_external_id;
        }
        $storeId = $this->getStore()->getId();
        return uniqid('fbe_magento_' . $storeId . '_');
    }

    /**
     * @return array|false|int|string|null
     */
    public function getStoreName()
    {
        $frontendName = $this->getStore()->getFrontendName();
        if ($frontendName !== 'Default') {
            return $frontendName;
        }
        $defaultStoreName = $this->getStore()->getGroup()->getName();
        $escapeStrings = ['\r', '\n', '&nbsp;', '\t'];
        $defaultStoreName =
            trim(str_replace($escapeStrings, ' ', $defaultStoreName));
        if (!$defaultStoreName) {
            $defaultStoreName = $this->getStore()->getName();
            $defaultStoreName =
                trim(str_replace($escapeStrings, ' ', $defaultStoreName));
        }
        if ($defaultStoreName && $defaultStoreName !== self::MAIN_WEBSITE_STORE
            && $defaultStoreName !== self::MAIN_STORE
            && $defaultStoreName !== self::MAIN_WEBSITE) {
            return $defaultStoreName;
        }
        return parse_url(self::getBaseUrl(), PHP_URL_HOST);
    }

    /**
     * @param $info
     */
    public function log($info)
    {
        $this->logger->info($info);
    }

    /**
     * @param \Exception $e
     */
    public function logException(\Exception $e)
    {
        $this->logger->error($e->getMessage());
        $this->logger->error($e->getTraceAsString());
        $this->logger->error($e);
    }

    /**
     * @return string|void|null
     */
    public function getAPIVersion()
    {
        $accessToken = $this->getAccessToken();
        if ($accessToken == null) {
            $this->log("can't find access token, won't get api update version ");
            return;
        }
        $api_version = null;
        try {

            $configRow = $this->configFactory->create()->load('fb/api/version');
            $api_version = $configRow ? $configRow->getConfigValue() : null;
            //$this->log("Current api version : ".$api_version);
            $versionLastUpdate = $configRow ? $configRow->getUpdateTime() : null;
            //$this->log("Version last update: ".$versionLastUpdate);
            $is_updated_version = $this->isUpdatedVersion($versionLastUpdate);
            if ($api_version && $is_updated_version) {
                //$this->log("Returning the version already stored in db : ".$api_version);
                return $api_version;
            }
            $this->curl->addHeader("Authorization", "Bearer " . $accessToken);
            $this->curl->get(self::FB_GRAPH_BASE_URL . 'api_version');
            //$this->log("The API call: ".self::FB_GRAPH_BASE_URL.'api_version');
            $response = $this->curl->getBody();
            //$this->log("The API reponse : ".json_encode($response));
            $decodeResponse = json_decode($response);
            $api_version = $decodeResponse->api_version;
            //$this->log("The version fetched via API call: ".$api_version);
            $this->saveConfig('fb/api/version', $api_version);

        } catch (\Exception $e) {
            $this->log("Failed to fetch latest api version with error " . $e->getMessage());
        }

        return $api_version ? $api_version : self::CURRENT_API_VERSION;
    }

    /*
     * TODO decide which ids we want to return for commerce feature
     * This function queries FBE assets and other commerce related assets. We have stored most of them during FBE setup,
     * such as BM, Pixel, catalog, profiles, ad_account_id. We might want to store or query ig_profiles,
     * commerce_merchant_settings_id, pages in the future.
     * API dev doc https://developers.facebook.com/docs/marketing-api/fbe/fbe2/guides/get-features
     * Here is one example response, we would expect commerce_merchant_settings_id as well in commerce flow
     * {"data":[{"business_manager_id":"12345","onsite_eligible":false,"pixel_id":"12333","profiles":["112","111"],
     * "ad_account_id":"111","catalog_id":"111","pages":["111"],"instagram_profiles":["111"]}]}
     *  usage: $_bm = $_assets['business_manager_ids'];
     */
    public function queryFBEInstalls($external_business_id = null)
    {
        if ($external_business_id == null) {
            $external_business_id = $this->getFBEExternalBusinessId();
        }
        $accessToken = $this->getAccessToken();
        $urlSuffix = "/fbe_business/fbe_installs?fbe_external_business_id=" . $external_business_id;
        $url = $this::FB_GRAPH_BASE_URL . $this->getAPIVersion() . $urlSuffix;
        $this->log($url);
        try {
            $this->curl->addHeader("Authorization", "Bearer " . $accessToken);
            $this->curl->get($url);
            $response = $this->curl->getBody();
            $this->log("The FBE Install reponse : " . json_encode($response));
            $decodeResponse = json_decode($response, true);
            $assets = $decodeResponse['data'][0];
        } catch (\Exception $e) {
            $this->log("Failed to query FBEInstalls" . $e->getMessage());
        }
    }

    /**
     * @param $pixelId
     * @param $pixelEvent
     */
    public function logPixelEvent($pixelId, $pixelEvent)
    {
        $this->log($pixelEvent . " event fired for Pixel id : " . $pixelId);
    }

    /**
     * @return array
     */
    public function deleteConfigKeys()
    {
        $response = [];
        $response['success'] = false;
        try {
            $connection = $this->resourceConnection->getConnection();
            $facebook_config = $this->resourceConnection->getTableName('facebook_business_extension_config');
            $sql = "DELETE FROM $facebook_config WHERE config_key NOT LIKE 'permanent%' ";
            $connection->query($sql);
            $response['success'] = true;
            $response['message'] = self::DELETE_SUCCESS_MESSAGE;
        } catch (\Exception $e) {
            $this->log($e->getMessage());
            $response['error_message'] = self::DELETE_FAILURE_MESSAGE;
        }
        return $response;
    }

    /**
     * @param $versionLastUpdate
     * @return bool|null
     */
    public function isUpdatedVersion($versionLastUpdate)
    {
        if (!$versionLastUpdate) {
            return null;
        }
        $monthsSinceLastUpdate = 3;
        try {
            $datetime1 = new \DateTime($versionLastUpdate);
            $datetime2 = new \DateTime();
            $interval = date_diff($datetime1, $datetime2);
            $interval_vars = get_object_vars($interval);
            $monthsSinceLastUpdate = $interval_vars['m'];
            $this->log("Months since last update : " . $monthsSinceLastUpdate);
        } catch (\Exception $e) {
            $this->log($e->getMessage());
        }
        // Since the previous version is valid for 3 months,
        // I will check to see for the gap to be only 2 months to be safe.
        return $monthsSinceLastUpdate <= 2;
    }

    /**
     * @param $accessToken
     * @return string
     */
    public function getCatalogBatchAPI($accessToken)
    {
        $catalogId = $this->getConfigValue('fbe/catalog/id');
        $external_business_id = $this->getFBEExternalBusinessId();
        if ($catalogId != null) {
            $catalog_path = "/" . $catalogId . "/items_batch";
        } else {
            $catalog_path = "/fbe_catalog/batch?fbe_external_business_id=" .
                $external_business_id;
        }
        $catalogBatchApi = self::FB_GRAPH_BASE_URL .
            $this->getAPIVersion($accessToken) .
            $catalog_path;
        $this->log("Catalog Batch API - " . $catalogBatchApi);
        return $catalogBatchApi;
    }

    /**
     * @return mixed
     */
    public function getStoreCurrencyCode()
    {
        return $this->getStore()->getCurrentCurrencyCode();
    }

    /**
     * @return string
     */
    public function isFBEInstalled()
    {
        $isFbeInstalled = $this->getConfigValue('fbe/installed');
        if ($isFbeInstalled) {
            return 'true';
        }
        return 'false';
    }

    /**
     * @param $pixelId
     * @return mixed
     */
    private function fetchAAMSettings($pixelId)
    {
        return AdsPixelSettings::buildFromPixelId($pixelId);
    }

    /**
     * @return AdsPixelSettings|null
     */
    public function getAAMSettings()
    {
        $settingsAsString = $this->getConfigValue('fbpixel/aam_settings');
        if ($settingsAsString) {
            $settingsAsArray = json_decode($settingsAsString, true);
            if ($settingsAsArray) {
                $settings = new AdsPixelSettings();
                $settings->setPixelId($settingsAsArray['pixelId']);
                $settings->setEnableAutomaticMatching($settingsAsArray['enableAutomaticMatching']);
                $settings->setEnabledAutomaticMatchingFields($settingsAsArray['enabledAutomaticMatchingFields']);
                return $settings;
            }
        }
        return null;
    }

    /**
     * @param $settings
     * @return false|string
     */
    private function saveAAMSettings($settings)
    {
        $settingsAsArray = [
            'enableAutomaticMatching' => $settings->getEnableAutomaticMatching(),
            'enabledAutomaticMatchingFields' => $settings->getEnabledAutomaticMatchingFields(),
            'pixelId' => $settings->getPixelId(),
        ];
        $settingsAsString = json_encode($settingsAsArray);
        $this->saveConfig('fbpixel/aam_settings', $settingsAsString);
        return $settingsAsString;
    }

    /**
     * @param $pixelId
     * @return false|string|null
     */
    public function fetchAndSaveAAMSettings($pixelId)
    {
        $settings = $this->fetchAAMSettings($pixelId);
        if ($settings) {
            return $this->saveAAMSettings($settings);
        }
        return null;
    }

    /**
     * Generates a map of the form : 4 => "Root > Mens > Shoes"
     *
     * @return array
     */
    public function generateCategoryNameMap()
    {
        $categories = $this->getObject(CategoryCollection::class)
            ->addAttributeToSelect('name')
            ->addAttributeToSelect('path')
            ->addAttributeToSelect('is_active')
            ->addAttributeToFilter('is_active', 1);
        $name = [];
        $breadcrumb = [];
        foreach ($categories as $category) {
            $entityId = $category->getId();
            $name[$entityId] = $category->getName();
            $breadcrumb[$entityId] = $category->getPath();
        }
        // Converts the product category paths to human readable form.
        // e.g.  "1/2/3" => "Root > Mens > Shoes"
        foreach ($name as $id => $value) {
            $breadcrumb[$id] = implode(" > ", array_filter(array_map(
                function ($innerId) use (&$name) {
                    return isset($name[$innerId]) ? $name[$innerId] : null;
                },
                explode("/", $breadcrumb[$id])
            )));
        }
        return $breadcrumb;
    }
}
