<?php

/**
 * KrmPesan PHP SDK.
 *
 * @version     3.5.0
 *
 * @see         https://github.com/KrmPesan/SDK-PHP
 *
 * @author      KrmPesan <support@krmpesan.com>
 * @copyright   2023 KrmPesan
 */

namespace KrmPesan;

use DateTime;
use DateTimeZone;
use Exception;

/**
 * KrmPesan Client Class For Handle REST API Request.
 *
 * @see https://docs.krmpesan.com/
 */
class ClientV3
{
  /**
   * Default Curl Timeout.
   *
   * @var int
   *
   * @see https://www.php.net/manual/en/function.curl-setopt
   */
  protected $timeout;

  /**
   * Default API Url.
   *
   * @var string
   */
  protected $apiUrl = 'https://api.krmpesan.app';

  /**
   * Default TimeZone For DateTime
   * Example: Asia/Jakarta.
   *
   * @var string
   */
  protected $timezone;

  /**
   * Store Token to File JSON Format.
   */
  protected $tokenFile;

  /**
   * API Token.
   *
   * @var string
   */
  protected $token;

  /**
   * Refresh Token.
   *
   * @var string
   */
  protected $refreshToken;

  /**
   * API Token.
   *
   * @var string
   */
  protected $deviceId;

  /**
   * Token Expired.
   *
   * @var string
   */
  protected $expiredAt;

  /**
   * Custom Request Header.
   *
   * @var array
   */
  protected $customHeader;

  /**
   * Construct Function.
   *
   * @param array $data
   */
  public function __construct(array $data)
  {
    $this->timezone = isset($data['timezone']) ? $data['timezone'] : 'Asia/Jakarta';

    // Set Token Path
    if (isset($data['tokenFile']) and !empty($data['tokenFile'])) {
      // check path directory is exist
      if (!is_dir($data['tokenFile'])) {
        throw new Exception('Directory not found.');
      }

      // save path
      $this->tokenFile = $data['tokenFile'] . '/token.json';

      // load token
      $this->getToken();
    } else {
      // Set Token (optional)
      $this->token = $data['idToken'];

      // Set DeviceId
      if (!isset($data['deviceId']) or empty($data['deviceId'])) {
        throw new Exception('DeviceId is required.');
      } else {
        $this->deviceId = $data['deviceId'];
      }

      // Set Refresh Token
      if (!isset($data['refreshToken']) or empty($data['refreshToken'])) {
        throw new Exception('Token is required.');
      } else {
        $this->refreshToken = $data['refreshToken'];
      }
    }

    // Set Custom Header
    $this->customHeader = $data['headers'] ?? null;

    // validate token
    $this->validateToken();
  }

  /**
   * Curl Post or Get Function.
   *
   * @param string $type
   * @param string $url
   * @param array  $form
   */
  private function action($type, $url, $form = null, $file = null)
  {
    // setup url
    $buildUrl = $this->apiUrl . '/' . $url;

    // set default header
    $headers = [];
    $headers[] = 'Content-Type: application/json';
    $headers[] = 'Authorization: Bearer ' . $this->token;

    // use custom header if not null
    if ($this->customHeader) {
      $headers = $this->customHeader;
    }

    // build curl instance
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $buildUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    if (isset($file)) {
      curl_setopt($ch, CURLOPT_INFILE, fopen($file, 'r'));
      curl_setopt($ch, CURLOPT_INFILESIZE, filesize($file));
    } else {
      $headers[] = 'Content-Type: application/json';
    }

    if (isset($form)) {
      curl_setopt($ch, CURLOPT_POSTFIELDS, $form);
    }

    // check action type
    if ($type == 'POST') {
      curl_setopt($ch, CURLOPT_POST, true);
    } elseif ($type == 'PUT') {
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    } elseif ($type == 'DELETE') {
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
    } else {
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    }

    // running curl
    $result = curl_exec($ch);

    // throw error
    if (curl_errno($ch)) {
      throw new Exception(curl_error($ch));
    }

    // stop connection
    curl_close($ch);

    // return result request
    return $result;
  }

  public function request($type, $url, $form = null)
  {
    try {
      $this->validateToken();

      return $this->action($type, $url, $form);
    } catch (Exception $e) {
      throw new Exception($e->getMessage());
    }
  }

  /**
   * Refresh Token.
   *
   * @return void
   */
  public function refreshToken()
  {
    $url = 'tokens?refresh_token=' . $this->refreshToken . '&device_key=' . $this->deviceId;
    $response = $this->action('GET', $url);
    $data = json_decode($response, true);
    $this->token = $data['IdToken'];

    // load token to internal function
    $this->storeToken();

    return $data;
  }

  public function getToken()
  {
    if (isset($this->tokenFile) and !empty($this->tokenFile)) {
      $getFile = file_get_contents($this->tokenFile);
      $parseFile = json_decode($getFile, true);

      $refreshToken = isset($parseFile['refreshToken']) ? $parseFile['refreshToken'] : null;
      $deviceId = isset($parseFile['deviceId']) ? $parseFile['deviceId'] : null;
      $idToken = isset($parseFile['idToken']) ? $parseFile['idToken'] : null;
      $expiredAt = isset($parseFile['expiredAt']) ? $parseFile['expiredAt'] : null;

      if (!$refreshToken) {
        throw new Exception('refreshToken Not Found at ' . $this->tokenFile);
      }

      // set refresh token
      $this->refreshToken = $refreshToken;

      if (!$deviceId) {
        throw new Exception('deviceId Not Found at ' . $this->tokenFile);
      }

      // set refresh token
      $this->deviceId = $deviceId;

      if (!$idToken xor !$expiredAt) {
        $this->refreshToken();
      } else {
        $this->token = $idToken;
        $this->expiredAt = $expiredAt;
      }
    }

    $result = [
      'idToken'      => $this->token,
      'refreshToken' => $this->refreshToken,
      'expiredAt'    => $this->expiredAt,
    ];

    return $result;
  }

  public function storeToken()
  {
    if (isset($this->tokenFile) and !empty($this->tokenFile)) {
      try {
        $getFile = file_get_contents($this->tokenFile);
        $parseFile = json_decode($getFile, true);

        $date = new DateTime('now', new DateTimeZone($this->timezone));
        $date->modify('+1 day');
        $parseFile['idToken'] = $this->token;
        $parseFile['expiredAt'] = $date->format('Y-m-d H:i:s');

        file_put_contents($this->tokenFile, json_encode($parseFile));
      } catch (Exception $e) {
        throw new Exception('tokenFile Error.!');
      }
    }
  }

  public function validateToken()
  {
    try {
      $date = new DateTime('now', new DateTimeZone($this->timezone));
      $now = $date->format('Y-m-d H:i:s');

      if ($now > $this->expiredAt) {
        $this->refreshToken();
      }

      // success
    } catch (Exception $e) {
      // error
      print_r($e);
    }
  }

  /**
   * Create Template Message.
   *
   * @param string $name
   * @param string $category    UTILLITY, AUTHENTICATION, MARKETING
   * @param string $description
   */
  public function createTemplate($name, $category, $description)
  {
    // build form
    $form = json_encode([
      'name'        => $name,
      'category'    => $category,
      'description' => $description,
    ]);

    return $this->request('POST', 'messages/template', $form);
  }

  /**
   * Create Template Language.
   *
   * @param string $slug
   * @param string $lang    en, id
   * @param string $message
   * @param array  $fields  ["fields-1", "fields-2", "fields-3"]
   * @param array  $header  [
   *                        "type" => "image|document",
   *                        "url" => "https://example.com/image.png"
   *                        ]
   * @param string $footer
   * @param array  $button
   */
  public function createTemplateLang($slug, $lang, $message, $fields, $header = null, $footer = null, $button = null)
  {
    // build form
    $form = json_encode([
      'slug'     => $slug,
      'language' => $lang,
      'message'  => $message,
      'fields'   => $fields,
      'header'   => isset($header) ? $header : null,
      'footer'   => isset($footer) ? $footer : null,
      'button'   => isset($button) ? $button : null,
    ]);

    return $this->request('POST', 'messages/template/lang', $form);
  }

  /**
   * Send Message Text Template with Parameter or Without Parameter.
   *
   * @param string|int $to
   * @param string     $templateLanguage
   * @param string     $templateName
   * @param array|null $body
   */
  public function sendMessageTemplateText($to, $templateName, $templateLanguage, $body = null)
  {
    // build form
    $form = [
      'phone'             => $to,
      'template_name'     => $templateName,
      'template_language' => $templateLanguage,
    ];

    // use body if not null
    if (isset($body) or !empty($body)) {
      $form['template'] = (object) [
        'body' => $body,
      ];
    }

    return $this->request('POST', 'messages', json_encode($form));
  }

  /**
   * Send Message Image.
   *
   * @param string|int $to
   * @param string     $templateLanguage
   * @param string     $templateName
   * @param array      $body
   * @param string     $image
   *
   * @return void
   */
  public function sendMessageTemplateImage($to, $templateName, $templateLanguage, $body, $image)
  {
    // build form
    $form = json_encode([
      'phone'             => $to,
      'template_name'     => $templateName,
      'template_language' => $templateLanguage,
      'template'          => (object) [
        'body'   => $body,
        'header' => [
          'type' => 'image',
          'url'  => $image,
        ],
      ],
    ]);

    return $this->request('POST', 'messages', $form);
  }

  /**
   * Send Message Document.
   *
   * @param string|int $to
   * @param string     $templateLanguage
   * @param string     $templateName
   * @param array      $body
   * @param string     $document
   *
   * @return void
   */
  public function sendMessageTemplateDocument($to, $templateName, $templateLanguage, $body, $document)
  {
    // build form
    $form = json_encode([
      'phone'             => $to,
      'template_name'     => $templateName,
      'template_language' => $templateLanguage,
      'template'          => (object) [
        'body'   => $body,
        'header' => [
          'type' => 'document',
          'url'  => $document,
        ],
      ],
    ]);

    return $this->request('POST', 'messages', $form);
  }

  /**
   * Send Message Button.
   *
   * @param string|int $to
   * @param string     $templateLanguage
   * @param string     $templateName
   * @param array      $body
   * @param string     $button
   *
   * @return void
   */
  public function sendMessageTemplateButton($to, $templateName, $templateLanguage, $body, $button)
  {
    // build form
    $form = json_encode([
      'phone'             => $to,
      'template_name'     => $templateName,
      'template_language' => $templateLanguage,
      'template'          => (object) [
        'body'    => $body,
        'buttons' => [
          'url' => $button,
        ],
      ],
    ]);

    return $this->request('POST', 'messages', $form);
  }

  /**
   * Send Reply Text.
   *
   * @param string|int $to
   * @param string     $text
   *
   * @return void
   */
  public function sendReplyText($to, $text)
  {
    // build form
    $form = json_encode([
      'phone' => $to,
      'reply' => (object) [
        'type' => 'text',
        'text' => $text,
      ],
    ]);

    return $this->request('POST', 'messages', $form);
  }

  /**
   * Send Reply Image.
   *
   * @param string|int $to
   * @param string     $image
   * @param string     $caption
   *
   * @return void
   */
  public function sendReplyImage($to, $image, $caption = '')
  {
    // build form
    $form = json_encode([
      'phone' => $to,
      'reply' => (object) [
        'type'    => 'image',
        'image'   => $image,
        'caption' => $caption,
      ],
    ]);

    return $this->request('POST', 'messages', $form);
  }

  /**
   * Send Reply document.
   *
   * @param string|int $to
   * @param string     $document
   *
   * @return void
   */
  public function sendReplyDocument($to, $document)
  {
    // build form
    $form = json_encode([
      'phone' => $to,
      'reply' => (object) [
        'type'     => 'image',
        'document' => $document,
      ],
    ]);

    return $this->request('POST', 'messages', $form);
  }

  /**
   * Get Device Data.
   */
  public function getDevice()
  {
    return $this->request('GET', 'devices');
  }

  /**
   * Get All Messages.
   */
  public function getMessages()
  {
    return $this->request('GET', 'messages');
  }

  public function upload($fileData)
  {
    // parse file and get file information
    // https://www.php.net/manual/en/function.realpath
    $file = realpath($fileData);
    // https://www.php.net/manual/en/function.basename
    $filename = basename($file);
    // https://www.php.net/manual/en/function.mime-content-type
    $filemime = mime_content_type($file);

    $presign = $this->request('POST', 'files/generate', json_encode([
      'filename' => $filename,
      'mime'     => $filemime,
      'expired'  => 30,
    ]));

    $resp = json_decode($presign, true);

    if (!isset($resp['data']) and !isset($resp['data']['url'])) {
      throw new Exception('Failed to generate presign url');
    }

    $url = $resp['data']['url'];
    $urlClean = explode('?', $url)[0];

    // Create a cURL handle
    $curl = curl_init();

    // Set the cURL options
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_PUT, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYSTATUS, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_INFILE, fopen($fileData, 'r'));
    curl_setopt($curl, CURLOPT_INFILESIZE, filesize($fileData));
    curl_setopt($curl, CURLOPT_HTTPHEADER, ["Content-Type: $filemime"]);

    // Execute the cURL request
    $response = curl_exec($curl);

    // Check if the request was successful
    if ($response === false) {
      throw new Exception('Error uploading file: ' . curl_error($curl));
    }

    // Close the cURL handle
    curl_close($curl);

    return $urlClean;
  }

  /**
   * Send Message Template Authentication.
   *
   * @param string|int $to
   * @param string     $templateName
   * @param string     $templateLanguage
   * @param string     $otp
   */
  public function sendMessageTemplateAuthentication($to, $templateName, $templateLanguage, $otp)
  {
    // build form
    $form = [
      'phone'             => $to,
      'template_name'     => $templateName,
      'template_language' => $templateLanguage,
      'template'          => (object) [
        'body'    => [$otp],
        'buttons' => [
          'otp_copy_code' => $otp,
        ],
      ],
    ];

    return $this->request('POST', 'messages', json_encode($form));
  }
}

