<?php

namespace Velis\Sms;

use Velis\App;
use Velis\Sms;
use InvalidArgumentException;
use RuntimeException;
use Exception;

/**
 * Lightweight API interface with the Amazon Simple Notification Service
 *
 */
class Amazon extends Sms implements SmsInterface
{
    /** @var string $access_key */
    private $access_key;
    /** @var string $secret_key */
    private $secret_key;

    /** @var string $protocol */
    private $protocol = 'https://'; // http is allowed
    /** @var string $endpoint */
    private $endpoint = ''; // Defaults to us-east-1

    /** @var array $endpoints */
    private $endpoints = array(
        'us-east-1' => 'sns.us-east-1.amazonaws.com',
        'us-west-2' => 'sns.us-west-2.amazonaws.com',
        'us-west-1' => 'sns.us-west-1.amazonaws.com',
        'eu-west-1' => 'sns.eu-west-1.amazonaws.com',
        'eu-central-1' => 'ec2.eu-central-1.amazonaws.com',
        'ap-southeast-1' => 'sns.ap-southeast-1.amazonaws.com',
        'ap-southeast-2' => 'sns.ap-southeast-2.amazonaws.com',
        'ap-northeast-1' => 'sns.ap-northeast-1.amazonaws.com',
        'sa-east-1' => 'sns.sa-east-1.amazonaws.com'
    );

    /**
     * Instantiate the object - set access_key and secret_key and set default region
     */
    public function __construct()
    {
        $this->access_key = App::$config->sms->amazonAccessKey;
        $this->secret_key = App::$config->sms->amazonPassKey;
        $this->setRegion(App::$config->sms->amazonRegion);
    }

    public function configIsCompleted(): bool
    {
        if (!App::$config->sms->amazonAccessKey) {
            return false;
        }

        if (!App::$config->sms->amazonPassKey) {
            return false;
        }

        return true;
    }

    /**
     * Set the SNS endpoint/region
     *
     * @link http://docs.amazonwebservices.com/general/latest/gr/index.html?rande.html
     * @param string $region
     * @return string
     * @throws InvalidArgumentException
     */
    public function setRegion($region)
    {
        if (!isset($this->endpoints[$region])) {
            throw new InvalidArgumentException('Region unrecognised');
        }

        return $this->endpoint = $this->endpoints[$region];
    }


    /**
     * Set SMS recipient number
     *
     * @param string $number
     * @return Velis_Model_Sms
     */
    public function setRecipientNumber($number)
    {
        $this->_recipientNumber = $number;
        return $this;
    }


    /**
     * Set SMS recipient number
     *
     * @param string $content
     * @return Velis_Model_Sms
     */
    public function setContent($content)
    {

        if ($this->_config['multipleSms']) {
            $this->_content = $content;
            return $this;
        }

        $i = 0;
        while (strlen($this->_content) < self::SINGLE_SMS_LENGTH) {
            if ('' == $part = mb_substr($content, $i++, 1)) {
                break;
            }
            $this->_content = $this->_content . $part;
        }
        return $this;
    }

    /**
     * Publish a message to a topic
     *
     * @link http://docs.amazonwebservices.com/sns/latest/api/API_Publish.html
     * @param string $phoneNo
     * @param string $message
     * @return string
     * @throws InvalidArgumentException
     */
    public function send()
    {
        if (empty($this->_recipientNumber) || empty($this->_content)) {
            throw new InvalidArgumentException('Must supply a PhoneNumber and Message to publish to a topic');
        }

        $params = array(
            'PhoneNumber' => $this->_recipientNumber,
            'Message' => $this->_content
        );

        if (!empty($messageStructure)) {
            $params['MessageStructure'] = $messageStructure;
        }

        $resultXml = $this->_request('Publish', $params);
        $this->log();
        return strval($resultXml->PublishResult->MessageId);
    }


    //
    // Private functions
    //

    /**
     * Perform and process a cURL request
     *
     * @param string $action
     * @param array $params [optional]
     * @return SimpleXMLElement
     * @throws Exception
     */
    private function _request($action, $params = array())
    {
        // Add in required params
        $params['Action'] = $action;
        $params['AWSAccessKeyId'] = $this->access_key;
        $params['Timestamp'] = gmdate('Y-m-d\TH:i:s.000\Z');
        $params['SignatureVersion'] = 2;
        $params['SignatureMethod'] = 'HmacSHA256';

        // Sort and encode into string
        uksort($params, 'strnatcmp');
        $queryString = '';
        foreach ($params as $key => $val) {
            $queryString .= "&{$key}=" . rawurlencode($val);
        }
        $queryString = substr($queryString, 1);

        // Form request string
        $requestString = "GET\n"
            . $this->endpoint . "\n"
            . "/\n"
            . $queryString;

        // Create signature - Version 2
        $params['Signature'] = base64_encode(
            hash_hmac('sha256', $requestString, $this->secret_key, true)
        );

        // Finally create request
        $request = $this->protocol . $this->endpoint . '/?' . http_build_query($params, '', '&');

        // Instantiate cUrl and perform request
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $request);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $output = curl_exec($ch);
        $info = curl_getinfo($ch);

        // Close cUrl
        curl_close($ch);

        // Load XML response
        $xmlResponse = simplexml_load_string($output);

        // Check return code
        if ($this->_checkGoodResponse($info['http_code']) === false) {
            // Response not in 200 range
            if (isset($xmlResponse->Error)) {
                // Amazon returned an XML error
                throw new RuntimeException(strval($xmlResponse->Error->Code) . ': ' . strval($xmlResponse->Error->Message), $info['http_code']);
            } else {
                // Some other problem
                throw new RuntimeException('There was a problem executing this request', $info['http_code']);
            }
        } else {
            // All good
            return $xmlResponse;
        }
    }

    /**
     * Check the curl response code - anything in 200 range
     *
     * @param int $code
     * @return bool
     */
    private function _checkGoodResponse($code)
    {
        return floor($code / 100) == 2;
    }

    /**
     * Transform the standard AmazonSNS XML array format into a normal array
     *
     * @param SimpleXMLElement $xmlArray
     * @return array
     */
    private function _processXmlToArray(SimpleXMLElement $xmlArray)
    {
        $returnArray = array();

        // Process into array
        foreach ($xmlArray as $xmlElement) {
            $elementArray = array();

            // Loop through each element
            foreach ($xmlElement as $key => $element) {
                // Use strval() to make sure no SimpleXMLElement objects remain
                $elementArray[$key] = strval($element);
            }

            // Store array of elements
            $returnArray[] = $elementArray;
        }

        return $returnArray;
    }
}
