<?php

namespace PortalPeople\netsuite\components;

use PortalPeople\netsuite\NetSuiteClient;
use yii\base\Component;
use yii\base\InvalidConfigException;

/**
 * NetSuite Component with OAuth 2.0 JWT Authentication
 * 
 * This component provides easy access to NetSuite API functionality in Yii2 applications
 * 
 * @package PortalPeople\netsuite\components
 */
class NetSuiteComponent extends Component
{
    /**
     * @var string NetSuite account ID
     */
    public $accountId;
    
    /**
     * @var string Client ID from NetSuite integration record
     */
    public $clientId;
    
    /**
     * @var string Client secret from NetSuite integration record
     */
    public $clientSecret;
    
    /**
     * @var string Certificate ID (Key ID) from NetSuite
     */
    public $certificateId;
    
    /**
     * @var string Path to private key file for JWT signing
     */
    public $privateKeyPath;
    
    /**
     * @var string NetSuite user internal ID for impersonation
     */
    public $userId;
    
    /**
     * @var string NetSuite REST API base URL (optional, will be auto-generated if not provided)
     */
    public $baseUrl;
    
    /**
     * @var NetSuiteClient NetSuite client instance
     */
    private $_client;
    
    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        
        if (empty($this->accountId)) {
            throw new InvalidConfigException('NetSuite account ID must be configured');
        }
        
        if (empty($this->clientId)) {
            throw new InvalidConfigException('NetSuite client ID must be configured');
        }
        
        if (empty($this->clientSecret)) {
            throw new InvalidConfigException('NetSuite client secret must be configured');
        }
        
        if (empty($this->certificateId)) {
            throw new InvalidConfigException('NetSuite certificate ID (Key ID) must be configured');
        }
        
        if (empty($this->privateKeyPath)) {
            throw new InvalidConfigException('Private key path must be configured');
        }
        
        if (empty($this->userId)) {
            throw new InvalidConfigException('NetSuite user ID must be configured');
        }
        
        // Resolve private key path relative to module directory if not absolute
        if (!$this->isAbsolutePath($this->privateKeyPath)) {
            $this->privateKeyPath = \Yii::getAlias('@app') . '/' . ltrim($this->privateKeyPath, '/');
        }
    }
    
    /**
     * Check if path is absolute
     * 
     * @param string $path
     * @return bool
     */
    private function isAbsolutePath($path)
    {
        return (PHP_OS_FAMILY === 'Windows') 
            ? preg_match('/^[a-zA-Z]:[\\\\\/]/', $path)
            : strpos($path, '/') === 0;
    }
    
    /**
     * Get NetSuite client instance
     * 
     * @return NetSuiteClient
     */
    public function getClient()
    {
        if ($this->_client === null) {
            $this->_client = new NetSuiteClient([
                'accountId' => $this->accountId,
                'clientId' => $this->clientId,
                'clientSecret' => $this->clientSecret,
                'certificateId' => $this->certificateId,
                'privateKeyPath' => $this->privateKeyPath,
                'userId' => $this->userId,
                'baseUrl' => $this->baseUrl
            ]);
        }
        
        return $this->_client;
    }
    
    /**
     * Create a new record in NetSuite
     * 
     * @param string $recordType The type of record to create (e.g., 'customer', 'item', 'salesorder')
     * @param array $data Record data
     * @return array Response data
     */
    public function createRecord($recordType, $data)
    {
        return $this->getClient()->post("/record/v1/{$recordType}", $data);
    }
    
    /**
     * Get a record from NetSuite by ID
     * 
     * @param string $recordType The type of record to retrieve
     * @param int $recordId The internal ID of the record
     * @return array Response data
     */
    public function getRecord($recordType, $recordId)
    {
        return $this->getClient()->get("/record/v1/{$recordType}/{$recordId}");
    }
    
    /**
     * Update a record in NetSuite
     * 
     * @param string $recordType The type of record to update
     * @param int $recordId The internal ID of the record
     * @param array $data Updated record data
     * @return array Response data
     */
    public function updateRecord($recordType, $recordId, $data)
    {
        return $this->getClient()->patch("/record/v1/{$recordType}/{$recordId}", $data);
    }
    
    /**
     * Delete a record from NetSuite
     * 
     * @param string $recordType The type of record to delete
     * @param int $recordId The internal ID of the record
     * @return array Response data
     */
    public function deleteRecord($recordType, $recordId)
    {
        return $this->getClient()->delete("/record/v1/{$recordType}/{$recordId}");
    }
    
    /**
     * Search for records in NetSuite
     * 
     * @param string $recordType The type of record to search
     * @param array $criteria Search criteria
     * @param array $options Search options (limit, offset, etc.)
     * @return array Response data
     */
    public function searchRecords($recordType, $criteria = [], $options = [])
    {
        $params = [];
        
        if (!empty($criteria)) {
            $params['q'] = json_encode($criteria);
        }
        
        if (!empty($options['limit'])) {
            $params['limit'] = $options['limit'];
        }
        
        if (!empty($options['offset'])) {
            $params['offset'] = $options['offset'];
        }
        
        return $this->getClient()->get("/query/v1/{$recordType}", $params);
    }
    
    /**
     * Execute a SuiteQL query
     * 
     * @param string $query SuiteQL query string
     * @param array $options Query options (limit, offset, etc.)
     * @return array Response data
     */
    public function executeQuery($query, $options = [])
    {
        $params = ['q' => $query];
        
        if (!empty($options['limit'])) {
            $params['limit'] = $options['limit'];
        }
        
        if (!empty($options['offset'])) {
            $params['offset'] = $options['offset'];
        }
        
        return $this->getClient()->get('/query/v1/suiteql', $params);
    }
    
    /**
     * Get record metadata
     * 
     * @param string $recordType The type of record
     * @return array Response data
     */
    public function getRecordMetadata($recordType)
    {
        return $this->getClient()->get("/record/v1/{$recordType}/metadata");
    }
    
    /**
     * Test the connection to NetSuite
     * 
     * @return bool True if connection is successful
     */
    public function testConnection()
    {
        try {
            $this->getClient()->get('/record/v1/account/1');
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Generate an RSA keypair for NetSuite OAuth2 (PEM private + public key)
     *
     * This helper creates an in-memory RSA keypair and optionally writes the
     * PEM files to the filesystem. It does NOT upload the public key to
     * NetSuite; you must paste the returned public PEM into the Integration
     * record (Setup → Integrations → Manage Integrations → OAuth 2.0
     * Certificates → New).
     *
    * @param string|null $saveDir Optional directory to save `private.pem` and the X.509 `certificate.pem`.
     * @param int $bits Key size in bits (2048 or 4096 recommended)
     * @param string|null $passphrase Optional passphrase to encrypt the private key (nullable)
     * @param bool $overwrite Whether to overwrite existing files in $saveDir
    * @return array [ 'private_pem'=>string, 'public_pem'=>string, 'certificate_pem'=>string, 'private_path'=>?string, 'public_path'=>?string, 'certificate_path'=>?string ]
     * @throws \RuntimeException on failure
     */
    public static function generateOAuthCertificate(?string $saveDir = null, int $bits = 2048, ?string $passphrase = null, bool $overwrite = false): array
    {
        $config = [
            'private_key_bits' => $bits,
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
        ];

        $res = openssl_pkey_new($config);
        if ($res === false) {
            throw new \RuntimeException('Failed to generate RSA keypair: ' . openssl_error_string());
        }

        // Export private key (PEM). If passphrase provided, export will be encrypted.
        $exported = openssl_pkey_export($res, $privatePem, $passphrase);
        if ($exported === false) {
            throw new \RuntimeException('Failed to export private key: ' . openssl_error_string());
        }

        $details = openssl_pkey_get_details($res);
        if (!isset($details['key'])) {
            throw new \RuntimeException('Failed to extract public key from keypair');
        }
        $publicPem = $details['key'];

        // Generate a minimal self-signed X.509 certificate (PEM) containing the public key.
        // NetSuite requires an X.509 certificate (BEGIN CERTIFICATE) rather than a raw public key.
        $dn = [
            'commonName' => 'NetSuite Integration',
        ];

        $csr = openssl_csr_new($dn, $res, ['digest_alg' => 'sha256']);
        if ($csr === false) {
            throw new \RuntimeException('Failed to create CSR: ' . openssl_error_string());
        }

        // Self-sign the CSR to create an X.509 certificate valid for 10 years
        $cert = openssl_csr_sign($csr, null, $res, 3650, ['digest_alg' => 'sha256']);
        if ($cert === false) {
            throw new \RuntimeException('Failed to self-sign certificate: ' . openssl_error_string());
        }

        openssl_x509_export($cert, $certificatePem);

        $privatePath = null;
        $publicPath = null;
        $certificatePath = null;
        if ($saveDir !== null) {
            $saveDir = rtrim($saveDir, DIRECTORY_SEPARATOR);
            if (!is_dir($saveDir)) {
                if (!mkdir($saveDir, 0700, true)) {
                    throw new \RuntimeException('Failed to create directory: ' . $saveDir);
                }
            }

            $privatePath = $saveDir . DIRECTORY_SEPARATOR . 'private.pem';
            $publicPath = $saveDir . DIRECTORY_SEPARATOR . 'public.pem';
            $certificatePath = $saveDir . DIRECTORY_SEPARATOR . 'certificate.pem';

            if ((!$overwrite) && (file_exists($privatePath) || file_exists($publicPath) || file_exists($certificatePath))) {
                throw new \RuntimeException('Certificate files already exist in ' . $saveDir . '. Use $overwrite=true to replace.');
            }

            if (file_put_contents($privatePath, $privatePem) === false) {
                throw new \RuntimeException('Failed to write private key to ' . $privatePath);
            }
            chmod($privatePath, 0600);

            if (file_put_contents($publicPath, $publicPem) === false) {
                // attempt cleanup of private file
                @unlink($privatePath);
                throw new \RuntimeException('Failed to write public key to ' . $publicPath);
            }
            chmod($publicPath, 0644);

            // write X.509 certificate PEM (what NetSuite expects)
            if (file_put_contents($certificatePath, $certificatePem) === false) {
                @unlink($privatePath);
                @unlink($publicPath);
                throw new \RuntimeException('Failed to write certificate to ' . $certificatePath);
            }
            chmod($certificatePath, 0644);
        }

        return [
            'private_pem' => $privatePem,
            'public_pem' => $publicPem,
            'certificate_pem' => $certificatePem,
            'private_path' => $privatePath,
            'public_path' => $publicPath,
            'certificate_path' => $certificatePath,
        ];
    }


    public static function SendOrderToNetsuite($id) {
        $cart = \backend\models\Cartsbe::find()->where(['id' => $id])->one();

        $netsuite = new \stdClass();
       // $netsuite->recordtype = 'salesorder';
        $netsuite->entity = new \stdClass();
        $netsuite->entity->id = 24435; //Live 19416;
        $netsuite->externalId = $cart->id . '/F';
        $netsuite->email = $cart->email;
        $netsuite->total = 0 + $cart->total;
        $netsuite->source = 'NGauge Commerce';
        $netsuite->tranDate = Date('c', strtotime('now')); //dateTime
        $netsuite->customForm = new \stdClass();
        $netsuite->customForm->id = 100;
        $netsuite->customFieldList = new \stdClass();
        $netsuite->currency = new \stdClass();
        $netsuite->currency->id = 1;
        
        $netsuite->message = $cart->message; // string;
        $netsuite->balance = $cart->amountout;
        $netsuite->otherRefNum = $cart->orderref.' '.substr($cart->contact, 0,35); //dateTime
        $netsuite->billingAddress = [
            'addressee' => $cart->fulladdresses['billing']->contact,
            'addr1' => $cart->fulladdresses['billing']->address1,
            'addr2' => $cart->fulladdresses['billing']->address2,
            'city' => substr($cart->fulladdresses['billing']->town, 0 , 50),
            'state' => substr($cart->fulladdresses['billing']->county, 0, 30),
            'country' => 'GB',
            'zip' => $cart->fulladdresses['billing']->postcode,
        ];
        $netsuite->shippingAddress = [
            'addressee' => $cart->fulladdresses['shipping']->contact,
            'addr1' => $cart->fulladdresses['shipping']->address1,
            'addr2' => $cart->fulladdresses['shipping']->address2,
            'city' => substr($cart->fulladdresses['shipping']->town, 0, 50),
            'state' => substr($cart->fulladdresses['shipping']->county,0 ,30),
            'country' => 'GB',
            'zip' => $cart->fulladdresses['shipping']->postcode,
            // 'addrPhone' => $cart->fulladdresses['shipping']->telephone, //No Perms
         ];
        $items = [];
        foreach ($cart->cartlines as $line) {
            $item = new \stdClass();
            $item->externalId = $line->id;
            $item->item = new \stdClass();
            $item->item->id = $line->product->data->netsuite->erpid;
            $item->item->name = $line->product_name;
            
            // Fix undefined variables
            $extraDesc = $line->product_description ?? '';
            $erpqty = $line->product->data->netsuite->erp_qty ? $line->product->data->netsuite->erp_qty : 1;
            $requested_date = $cart->requested_date ?? 'now';
            
            $item->description = trim($line->product_name . ' ' . $line->product_description . ' ' . $extraDesc);
            $item->quantity = $line->qty * $erpqty;
            $pricevat = ($line->price * $item->quantity) / (1 + (20 / 100)); // Fix VAT calculation
            $vat = $pricevat / $item->quantity;
           // $item->price = floatval($line->price);
            $item->rate = ($line->price - $vat) / $erpqty;
            //$item->quantity = floatval($line->qty);
            $item->requestedDate = date('c', strtotime('now')); // Add missing semicolon
            $item->expectedShipDate = date('c', strtotime($requested_date));
            
           // $item->units = new \stdClass();
           // $item->units->internalId = 'Each'; //$line->product->data->netsuite->unit ?? 'Each'; // Fix variable name
            
            $item->custcolweb_qty_sold = $item->quantity; // Fix property name
            $item->custcolweb_description = $item->description;
            $item->custcol_po_rate = $line->product->data->netsuite->erp_cost <> 0 ? floatval($line->product->data->netsuite->erp_cost) : 0.0;
            $item->custcol_po_vendor = $line->product->data->netsuite->supplierid ?? 0.0;
            $netsuite->custbody_exp_ship_date_so = Date('c', strtotime($line->requested_date));
            $items[] = $item; // Add item to array
        }
        //$netsuite->custbody_ahs_order_source = 'NGauge Commerce';
        $netsuite->custcol_po_vendor = 38051;
        
        $netsuite->item = new \stdClass();
        $netsuite->item->items = $items;

        $netsuite->memo = 'TEL:' . $cart->fulladdresses['shipping']->telephone . '    Email:' . $cart->email . '    MSG:' . $cart->message;
        
        $response = \common\components\NetSuiteApi::post('https://4465021.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder', get_object_vars($netsuite));
        Yii::Warning($response->content);
        return $response;
    }
}