<?php

class GmailApi extends RestApi {

  private $header;
  private $accessToken;
  private $refreshToken;
  private $clientId;
  private $clientSecret;
  private $tokenExpires;
  private $registry;
  private $load;
  private $config;
  private $log;
  private $scopes = [
      'https://www.googleapis.com/auth/gmail.send'
  ];
  private $authUrl = "https://accounts.google.com/o/oauth2/v2/auth";
  private $tokenUrl = "https://oauth2.googleapis.com/token";
  private $baseUrl = "https://gmail.googleapis.com/gmail/v1/";
  private $sendMessageUri = "users/me/messages/send";
  private $key = "9247yf9ouih0o8iyfd9283ur-2kecpijh2038u0r92";
  private $method = "aes-256-cbc";

  public function __construct($registry) {
    $this->registry = $registry;
    $this->load     = $registry->get('load');
    $this->config   = $registry->get('config');
    $this->log      = $registry->get('log');

    $this->setCredentials();
    $this->setHeader();
    $this->checkAccessToken();
  }

  public function getLoginUrl()
  {
      $params = [
          'client_id'     =>  $this->clientId,
          'redirect_uri'  =>  $this->urlloja->link('', '', 'NONSSL', false) . 'gmailauth',
          'response_type' =>  'code',
          'scope'         =>  implode(',', $this->scopes),
          'access_type'   =>  'offline',        
          'prompt'        =>  'consent',        
          'state'         =>  'seguranca_csrf',
      ];

      $authUrl = $this->authUrl . '?' . http_build_query($params);

      return $authUrl;
  }

  public function getAccessToken($code)
  {
      $this->setHeader(false);

      $param = json_encode([
          "client_id" => $this->clientId,
          "client_secret" => $this->clientSecret,
          "redirect_uri" => $this->urlloja->link('', '', 'NONSSL', false) . 'gmailauth',
          "code" => $code,
          "grant_type" => "authorization_code"
      ]);

      $data = $this->callAPI($this->tokenUrl, 'POST', $param, $this->getHeader(), null, $retorno);

      if ($this->error) {
          return '<strong>Error:</strong> Failed to get access code. Please check your application setting. <br> <strong>Message from Mercadolivre:</strong>' . $this->error;
      } else {
          if (is_object($data) && property_exists($data, 'access_token')) {
              $accessToken = $data->access_token;
              $tokenExpire = $data->expires_in;
              $refreshToken = $data->refresh_token;
              $this->setAccess($accessToken, $refreshToken, $tokenExpire);
          }
          return '';
      }
  }

  public function setAccess($accessToken, $refreshToken, $tokenExpire)
  {
      $this->load->type('tipocomum');
      $this->load->model('setting/setting');

      $data = array(
          'gmail_access_token' => $accessToken,
          'gmail_refresh_token' => $refreshToken,
          'gmail_token_expire' => time() + $tokenExpire,
          'gmail_new_customer' => Type_Status::Tp_Habilitado
      );

      $this->model_setting_setting->editSettingForKey('config', $data);

      $this->accessToken = $accessToken;
      $this->refreshToken = $refreshToken;
      $this->tokenExpires = $tokenExpire;
  }

  public function getAccess()
  {
      if ($this->accessToken && $this->refreshToken) {
          $this->checkAccessToken();
          return true;
      } else {
          return false;
      }
  }

  public function __get($name)
  {
      return $this->registry->get($name);
  }

  private function checkAccessToken(): void 
  {
    if ($this->tokenExpires < time()) {
      $this->refreshAccessToken();
    }
  }

  private function setCredentials(): void 
  {
    $this->clientId = $this->config->get('gmail_client_id') ?? $this->clientId;
    $this->clientSecret = $this->config->get('gmail_client_secret') ? $this->decryptData($this->config->get('gmail_client_secret')) : $this->clientSecret;
    $this->accessToken = $this->config->get('gmail_access_token') ?? $this->accessToken;
    $this->refreshToken = $this->config->get('gmail_refresh_token') ?? $this->refreshTokenToken;
    $this->tokenExpires = $this->config->get('gmail_token_expire') ?? time();

    if ($this->config->get('gmail_json_config')) {
        $configs = $this->decryptData($this->config->get('gmail_json_config'));
        $configs = json_decode($configs, true);
        $configs = $configs['web'] ?? [];
        if (!empty($configs)) {
          $this->clientId = $configs['client_id'] ?? $this->clientId;
          $this->clientSecret = $configs['client_secret'] ?? $this->clientSecret;
          $this->authUrl = $configs['auth_uri'] ?? $this->authUrl;
          $this->tokenUrl = $configs['token_uri'] ?? $this->tokenUrl;
        }
    } 
  }

  private function setHeader($auth = true, $data = null): void 
  {
    $this->header =[
      "Content-Type: application/json",
      "Accept: application/json"
    ];

    if ($auth && $this->accessToken) {
        $this->header[] = "Authorization: Bearer " . $this->accessToken;
    }

    if ($data) {
        $this->header[] =  "Content-Length: " . strlen($data);
    }
  }

  public function refreshAccessToken(): void 
  {
    if (!$this->config->get('config_mail_protocol') || $this->config->get('config_mail_protocol') != 'gmail') {
      return;
    }
    $this->setHeader(false);
    try{
      $dataSend = json_encode([
        'client_id'     => $this->clientId,
        'client_secret' => $this->clientSecret,
        'refresh_token' => $this->refreshToken,
        'grant_type'    => 'refresh_token',
      ]);
      
      $data = parent::callAPI($this->tokenUrl, 'POST', $dataSend, $this->getHeader(), false, $retorno);
      
      if (isset($data->error) && $data->error) {
        $this->log->write(json_encode($data->error, JSON_UNESCAPED_UNICODE));
        throw new Exception('Erro refresh token: ' . json_encode($data->error, JSON_UNESCAPED_UNICODE));
      }
      
      $this->load->type('tipocomum');
      $this->load->model('setting/setting');

      $dataToSave = array(
        'gmail_access_token' => $data->access_token,
        'gmail_refresh_token' => $data->refresh_token ?? $this->refreshToken,
        'gmail_token_expire' => time() + $data->expires_in,
        'gmail_refresh_token_expires' => time() + $data->refresh_token_expires_in
      );
      
      $this->model_setting_setting->editSettingForKey('config', $dataToSave);
      $this->setCredentials();
    }
    catch (Exception $ex) {
        $error = 'Erro refresh token Gmail:';
        $error.= 'Mensagem: ' . $ex->getMessage();
        $this->log->write(json_encode($error, JSON_UNESCAPED_UNICODE));
        return;
    }
  }

  public function sendMessage($data = []): bool 
  {
    try{

      $message = $this->convertMessage($data);
      
      if (!$message) {
        throw new Exception("Erro ao enviar email Gmail");
      }

      $url = $this->baseUrl . $this->sendMessageUri;
      
      $dataSend = json_encode(["raw" => $message]);
      
      $this->setHeader(true);

      $data = parent::callAPI($url, 'POST', $dataSend, $this->getHeader(), false, $retorno);

      if ($retorno == 401 || $retorno == 403) {
        $message = "Usuário não tem permissão para enviar email, consulte sua conta Gmail";
        throw new Exception($message, $retorno);
      }

      if (!$data || (isset($data->error) && $data->error)) {
         throw new Exception("Erro ao enviar email Gmail" . (isset($data->error->message) ? ": " . $data->error->message : ""));
      }
      return true;
    }
    catch (Exception $ex) {
        $error = 'Erro ao enviar email Gmail:';
        $error.= 'Mensagem: ' . $ex->getMessage();
        $this->log->write(json_encode($error, JSON_UNESCAPED_UNICODE));
        return false;
    }
  }

  public function convertMessage($infos = []) {
    if (empty($infos)) {
      return false;
    }

    if (!isset($infos['to']) || !isset($infos['subject']) || !isset($infos['message'])) {
      return false;
    }

    $rawMessage = "To: " . (is_array($infos['to']) ? implode(', ', $infos['to']) : $infos['to']) . "\r\n";
    $rawMessage .= "Subject: =?utf-8?B?" . base64_encode($infos['subject']) . "?=\r\n";
    $rawMessage .= "MIME-Version: 1.0\r\n";

    if (!empty($infos['attachments']) && is_array($infos['attachments'])) {
      $delimitador = md5(time()); 

      $rawMessage .= "Content-Type: multipart/mixed; boundary=\"$delimitador\"\r\n\r\n";
  
      $rawMessage .= "--$delimitador\r\n";
      $rawMessage .= "Content-Type: text/plain; charset=UTF-8\r\n";
      $rawMessage .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
      $rawMessage .= $infos['message']."\r\n\r\n";
  
      foreach ($infos['attachments'] as $attachment) {
        $nomeArquivo = $attachment['name'];
        $conteudo = $attachment['contentBytes'];
        $mimeType = $attachment['contentType'];

        $rawMessage .= "--$delimitador\r\n";
        $rawMessage .= "Content-Type: $mimeType; name=\"$nomeArquivo\"\r\n";
        $rawMessage .= "Content-Disposition: attachment; filename=\"$nomeArquivo\"\r\n";
        $rawMessage .= "Content-Transfer-Encoding: base64\r\n\r\n";
        $rawMessage .= $conteudo . "\r\n";
    
        $rawMessage .= "--$delimitador--";
      }
    } else {
      $rawMessage .= "Content-Type: ". ($infos['type'] == 'HTML' ? 'text/html' : 'text/plain') ."; charset=utf-8\r\n\r\n";
      $rawMessage .= $infos['message'];
    }

    $base64Message = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($rawMessage));

    return $base64Message;
  }

  private function getHeader():array 
  {
    return $this->header;
  }

  public function encryptData($data){
    if ($data) {
      $ivlen = openssl_cipher_iv_length($this->method);
      $iv = random_bytes($ivlen);

      $encryptData = openssl_encrypt($data, $this->method, $this->key, 0, $iv);

      return base64_encode($iv . $encryptData);
    }
    return "";
  }

  public function decryptDataOld($data){ //Para pegar chaves encriptadas anteriormente sem o IV
    if ($data) {
      $textoCripto = explode('::', $data, 2);
      if (count($textoCripto) > 1) {
        list($textoCripto, $iv) = $textoCripto;
      } else {
        $textoCripto = $textoCripto[0];
      }

      $decryptData = openssl_decrypt($textoCripto, $this->method, $this->key, 0, $iv ?? "");

      return $decryptData;
    }
    return "";
  }

  public function decryptData($data){
    if ($data) {
      $decode = base64_decode($data);
      $ivlen = openssl_cipher_iv_length($this->method);

      $ivFromMessage = substr($decode, 0, $ivlen);

      $textoCripto = substr($decode, $ivlen);

      $decryptData = openssl_decrypt($textoCripto, $this->method, $this->key, 0, $ivFromMessage);

      if (!$decryptData) {
        return $this->decryptDataOld($data);
      }
      return $decryptData;
    }
    return "";
  }
}
