<?php
/**
 * Magento REST API Client
 *
 * @category    PortalPeople
 * @package     Magento
 * @author      Portal People Development Team
 * @copyright   Copyright (c) 2025 Portal People (https://portalpeople.co.uk)
 */

namespace PortalPeople\magento\components;

use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\httpclient\Client;
use yii\httpclient\Exception as HttpException;

/**
 * Magento Client Component
 * 
 * Handles authentication and API calls to Magento REST API
 */
class MagentoClient extends Component
{
    /**
     * @var string Magento API base URL
     */
    public $apiUrl;

    /**
     * @var string Magento API access token
     */
    public $accessToken;

    /**
     * @var string Magento admin username (optional, for token generation)
     */
    public $adminUsername;

    /**
     * @var string Magento admin password (optional, for token generation)
     */
    public $adminPassword;

    /**
     * @var string Store code
     */
    public $storeCode = 'default';

    /**
     * @var bool Enable debug mode
     */
    public $debug = false;

    /**
     * @var int Request timeout in seconds
     */
    public $timeout = 30;

    /**
     * @var string Access token for API calls
     */
    private $currentToken;

    /**
     * @var Client HTTP client instance
     */
    private $httpClient;

    /**
     * @var int Token expiration time
     */
    private $tokenExpiration;

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        if (empty($this->apiUrl)) {
            throw new InvalidConfigException('Magento API URL must be configured');
        }

        // Either accessToken OR username/password must be provided
        if (empty($this->accessToken) && (empty($this->adminUsername) || empty($this->adminPassword))) {
            throw new InvalidConfigException('Either accessToken or admin credentials must be configured');
        }

        $this->httpClient = new Client([
            'baseUrl' => rtrim($this->apiUrl, '/') . '/rest/',
            'requestConfig' => [
                'format' => Client::FORMAT_JSON
            ],
            'responseConfig' => [
                'format' => Client::FORMAT_JSON
            ]
        ]);
    }

    /**
     * Get access token for API authentication
     * 
     * @return string
     * @throws HttpException
     */
    public function getAccessToken()
    {
        // If we have a pre-configured access token, use it
        if (!empty($this->accessToken)) {
            $this->currentToken = $this->accessToken;
            return $this->currentToken;
        }

        // Return cached token if still valid
        if ($this->currentToken && $this->tokenExpiration > time()) {
            return $this->currentToken;
        }

        // Generate new token using username/password
        if (empty($this->adminUsername) || empty($this->adminPassword)) {
            throw new HttpException('No access token provided and no admin credentials available for token generation');
        }

        $this->log('Requesting new access token from Magento API using admin credentials');

        try {
            $response = $this->httpClient->post('V1/integration/admin/token', [
                'username' => $this->adminUsername,
                'password' => $this->adminPassword
            ])->send();

            if (!$response->isOk) {
                throw new HttpException('Failed to get access token: ' . $response->statusCode . ' ' . $response->content);
            }

            $this->currentToken = trim($response->data, '"');
            $this->tokenExpiration = time() + 3600; // Token valid for 1 hour

            $this->log('Successfully obtained access token');

            return $this->currentToken;

        } catch (\Exception $e) {
            $this->log('Error getting access token: ' . $e->getMessage(), 'error');
            throw new HttpException('Failed to authenticate with Magento API: ' . $e->getMessage());
        }
    }

    /**
     * Make authenticated API request
     * 
     * @param string $method HTTP method
     * @param string $endpoint API endpoint
     * @param array $data Request data
     * @param array $params Query parameters
     * @return mixed
     * @throws HttpException
     */
    public function request($method, $endpoint, $data = [], $params = [])
    {
        $token = $this->getAccessToken();
        
        $endpoint = ltrim($endpoint, '/');
        $url = $this->storeCode . '/' . $endpoint;

        $this->log("Making {$method} request to: {$url}");

        try {
            $request = $this->httpClient->createRequest()
                ->setMethod($method)
                ->setUrl($url)
                ->addHeaders([
                    'Authorization' => 'Bearer ' . $token,
                    'Content-Type' => 'application/json'
                ]);

            if (!empty($data)) {
                $request->setData($data);
            }

            if (!empty($params)) {
                $request->setData($params);
            }

            $response = $request->send();

            if (!$response->isOk) {
                $errorMsg = "API request failed: {$response->statusCode}";
                if (!empty($response->content)) {
                    $errorMsg .= " - " . $response->content;
                }
                throw new HttpException($errorMsg);
            }

            $this->log("API request successful: {$response->statusCode}");

            return $response->data;

        } catch (\Exception $e) {
            $this->log("API request error: " . $e->getMessage(), 'error');
            throw $e;
        }
    }

    /**
     * Get product by SKU
     * 
     * @param string $sku
     * @return array|null
     */
    public function getProduct($sku)
    {
        try {
            return $this->request('GET', "V1/products/{$sku}");
        } catch (HttpException $e) {
            if (strpos($e->getMessage(), '404') !== false) {
                return null; // Product not found
            }
            throw $e;
        }
    }

    /**
     * Create product
     * 
     * @param array $productData
     * @return array
     */
    public function createProduct($productData)
    {
        return $this->request('POST', 'V1/products', ['product' => $productData]);
    }

    /**
     * Update product
     * 
     * @param string $sku
     * @param array $productData
     * @return array
     */
    public function updateProduct($sku, $productData)
    {
        // Magento ProductRepository::save() requires attribute_set_id even on partial updates.
        // Use getProduct() which handles 404 gracefully (returns null).
        if (!isset($productData['attribute_set_id'])) {
            $existing = $this->getProduct($sku);
            if ($existing === null) {
                throw new HttpException("Cannot update product '{$sku}': product not found in Magento (404). Create it first.");
            }
            if (!empty($existing['attribute_set_id'])) {
                $productData['attribute_set_id'] = $existing['attribute_set_id'];
            }
        }

        return $this->request('PUT', "V1/products/{$sku}", ['product' => $productData]);
    }

    /**
     * Update product price
     * 
     * @param string $sku
     * @param float $price
     * @return array
     */
    public function updateProductPrice($sku, $price)
    {
        return $this->updateProduct($sku, ['price' => $price]);
    }

    /**
     * Update product stock
     * 
     * @param string $sku
     * @param float $qty
     * @param bool $inStock
     * @return array
     */
    public function updateProductStock($sku, $qty, $inStock = true)
    {
        return $this->request('PUT', "V1/products/{$sku}/stockItems/1", [
            'stockItem' => [
                'qty' => $qty,
                'is_in_stock' => $inStock
            ]
        ]);
    }

    /**
     * Search products
     * 
     * @param array $searchCriteria
     * @return array
     */
    public function searchProducts($searchCriteria = [])
    {
        $params = [];
        
        if (!empty($searchCriteria)) {
            $params['searchCriteria'] = $searchCriteria;
        }

        return $this->request('GET', 'V1/products', [], $params);
    }

    /**
     * Get product attributes
     * 
     * @return array
     */
    public function getProductAttributes()
    {
        return $this->request('GET', 'V1/products/attributes');
    }

    /**
     * Search for a customer by email address
     *
     * @param string $email
     * @return array|null Customer data, or null if not found
     */
    public function searchCustomerByEmail($email)
    {
        try {
            $result = $this->request('GET', 'V1/customers/search', [], [
                'searchCriteria[filterGroups][0][filters][0][field]'          => 'email',
                'searchCriteria[filterGroups][0][filters][0][value]'          => $email,
                'searchCriteria[filterGroups][0][filters][0][condition_type]' => 'eq',
            ]);
            return !empty($result['items']) ? $result['items'][0] : null;
        } catch (\Exception $e) {
            if (strpos($e->getMessage(), '404') !== false) {
                return null;
            }
            throw $e;
        }
    }

    /**
     * Create a customer in Magento 2
     *
     * @param array       $customerData  Customer fields (email, firstname, lastname, addresses, etc.)
     * @param string|null $password      If null, Magento will require a password reset on first login
     * @return array
     */
    public function createCustomer($customerData, $password = null)
    {
        $payload = ['customer' => $customerData];
        if ($password !== null) {
            $payload['password'] = $password;
        }
        return $this->request('POST', 'V1/customers', $payload);
    }

    /**
     * Update a customer in Magento 2
     *
     * @param int   $id
     * @param array $customerData
     * @return array
     */
    public function updateCustomer($id, $customerData)
    {
        return $this->request('PUT', "V1/customers/{$id}", ['customer' => $customerData]);
    }

    /**
     * Test API connection
     * 
     * @return bool
     */
    public function testConnection()
    {
        try {
            $this->getAccessToken();
            return true;
        } catch (\Exception $e) {
            $this->log('Connection test failed: ' . $e->getMessage(), 'error');
            return false;
        }
    }

    /**
     * Log message
     * 
     * @param string $message
     * @param string $level
     */
    protected function log($message, $level = 'info')
    {
        if ($this->debug) {
            Yii::getLogger()->log($message, \yii\log\Logger::LEVEL_INFO, 'magento-api');
        }

        if ($level === 'error') {
            Yii::error($message, 'magento-api');
        }
    }

    /**
     * Get available product fields that can be synced
     * 
     * @return array List of available product fields with descriptions
     */
    public function getAvailableProductFields()
    {
        // Try cache first
        $cacheKey = 'magento.api.product_fields.' . md5($this->apiUrl . $this->storeCode);
        $cachedFields = Yii::$app->cache ? Yii::$app->cache->get($cacheKey) : false;
        
        if ($cachedFields !== false) {
            $this->log('Product fields loaded from cache', 'info');
            return $cachedFields;
        }
        
        try {
            // Get product attributes from Magento API
            $response = $this->httpClient->createRequest()
                ->setMethod('GET')
                ->setUrl('products/attributes')
                ->setData(['searchCriteria' => []])
                ->addHeaders(['Authorization' => 'Bearer ' . $this->getAccessToken()])
                ->send();

            $fields = [];
            
            if ($response->isOk && isset($response->data['items'])) {
                foreach ($response->data['items'] as $attribute) {
                    // Only include attributes that can be used in product updates
                    if ($this->isEditableAttribute($attribute)) {
                        $fields[$attribute['attribute_code']] = [
                            'code' => $attribute['attribute_code'],
                            'label' => $attribute['default_frontend_label'] ?? $attribute['attribute_code'],
                            'type' => $attribute['frontend_input'] ?? 'text',
                            'required' => $attribute['is_required'] ?? false,
                            'scope' => $attribute['scope'] ?? 'global'
                        ];
                    }
                }
            }

            // Add common fields that are always available
            $commonFields = $this->getCommonProductFields();
            $fields = array_merge($commonFields, $fields);

            // Sort by label for better UX
            uasort($fields, function($a, $b) {
                return strcmp($a['label'], $b['label']);
            });

            // Cache the results for 1 hour
            if (Yii::$app->cache) {
                Yii::$app->cache->set($cacheKey, $fields, 3600);
                $this->log('Product fields cached for 1 hour', 'info');
            }

            return $fields;
            uasort($fields, function($a, $b) {
                return strcmp($a['label'], $b['label']);
            });

            return $fields;

        } catch (\Exception $e) {
            $this->log('Failed to get product attributes: ' . $e->getMessage(), 'error');
            
            // Return common fields as fallback
            return $this->getCommonProductFields();
        }
    }

    /**
     * Get common product fields that are always available
     * 
     * @return array
     */
    protected function getCommonProductFields()
    {
        return [
            'sku' => [
                'code' => 'sku',
                'label' => 'SKU',
                'type' => 'text',
                'required' => true,
                'scope' => 'global'
            ],
            'name' => [
                'code' => 'name',
                'label' => 'Product Name',
                'type' => 'text',
                'required' => true,
                'scope' => 'store'
            ],
            'price' => [
                'code' => 'price',
                'label' => 'Price',
                'type' => 'price',
                'required' => false,
                'scope' => 'global'
            ],
            'description' => [
                'code' => 'description',
                'label' => 'Description',
                'type' => 'textarea',
                'required' => false,
                'scope' => 'store'
            ],
            'short_description' => [
                'code' => 'short_description',
                'label' => 'Short Description',
                'type' => 'textarea',
                'required' => false,
                'scope' => 'store'
            ],
            'weight' => [
                'code' => 'weight',
                'label' => 'Weight',
                'type' => 'text',
                'required' => false,
                'scope' => 'global'
            ],
            'status' => [
                'code' => 'status',
                'label' => 'Status',
                'type' => 'select',
                'required' => true,
                'scope' => 'store',
                'options' => ['1' => 'Enabled', '2' => 'Disabled']
            ],
            'visibility' => [
                'code' => 'visibility',
                'label' => 'Visibility',
                'type' => 'select',
                'required' => true,
                'scope' => 'store',
                'options' => [
                    '1' => 'Not Visible Individually',
                    '2' => 'Catalog',
                    '3' => 'Search',
                    '4' => 'Catalog, Search'
                ]
            ],
            'tax_class_id' => [
                'code' => 'tax_class_id',
                'label' => 'Tax Class',
                'type' => 'select',
                'required' => false,
                'scope' => 'global'
            ],
            'meta_title' => [
                'code' => 'meta_title',
                'label' => 'Meta Title',
                'type' => 'text',
                'required' => false,
                'scope' => 'store'
            ],
            'meta_description' => [
                'code' => 'meta_description',
                'label' => 'Meta Description',
                'type' => 'textarea',
                'required' => false,
                'scope' => 'store'
            ],
            'meta_keyword' => [
                'code' => 'meta_keyword',
                'label' => 'Meta Keywords',
                'type' => 'text',
                'required' => false,
                'scope' => 'store'
            ],
            'url_key' => [
                'code' => 'url_key',
                'label' => 'URL Key',
                'type' => 'text',
                'required' => false,
                'scope' => 'store'
            ]
        ];
    }

    /**
     * Check if attribute is editable via API
     * 
     * @param array $attribute
     * @return bool
     */
    protected function isEditableAttribute($attribute)
    {
        // Skip system attributes that can't be edited
        $systemAttributes = [
            'entity_id', 'entity_type_id', 'attribute_set_id', 'type_id',
            'created_at', 'updated_at', 'has_options', 'required_options'
        ];

        if (in_array($attribute['attribute_code'], $systemAttributes)) {
            return false;
        }

        // Skip attributes that are not user-defined and not in common fields
        if (($attribute['is_user_defined'] ?? false) === false) {
            $commonCodes = array_keys($this->getCommonProductFields());
            if (!in_array($attribute['attribute_code'], $commonCodes)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get categorized field list for better organization
     * 
     * @return array
     */
    public function getCategorizedProductFields()
    {
        $fields = $this->getAvailableProductFields();
        
        $categories = [
            'basic' => [
                'label' => 'Basic Information',
                'fields' => []
            ],
            'pricing' => [
                'label' => 'Pricing',
                'fields' => []
            ],
            'content' => [
                'label' => 'Content & SEO',
                'fields' => []
            ],
            'attributes' => [
                'label' => 'Product Attributes',
                'fields' => []
            ],
            'other' => [
                'label' => 'Other Fields',
                'fields' => []
            ]
        ];

        foreach ($fields as $code => $field) {
            if (in_array($code, ['sku', 'name', 'status', 'visibility', 'weight'])) {
                $categories['basic']['fields'][$code] = $field;
            } elseif (in_array($code, ['price', 'special_price', 'tax_class_id'])) {
                $categories['pricing']['fields'][$code] = $field;
            } elseif (in_array($code, ['description', 'short_description', 'meta_title', 'meta_description', 'meta_keyword', 'url_key'])) {
                $categories['content']['fields'][$code] = $field;
            } elseif ($field['type'] === 'select' || $field['type'] === 'multiselect') {
                $categories['attributes']['fields'][$code] = $field;
            } else {
                $categories['other']['fields'][$code] = $field;
            }
        }

        // Remove empty categories
        foreach ($categories as $key => $category) {
            if (empty($category['fields'])) {
                unset($categories[$key]);
            }
        }

        return $categories;
    }
}