<?php

class OutlookApi extends RestApi {

  private $header;
  private $accessToken;
  private $refreshToken;
  private $tenantId;
  private $senderEmail;
  private $clientId;
  private $clientSecret;
  private $tokenExpires;
  private $authType = Type_OutlookTypeAuth::Tp_Delegated; 
  private $registry;
  private $load;
  private $config;
  private $log;
  private $scopes = [
    Type_OutlookTypeAuth::Tp_Delegated => ['openid profile offline_access https://graph.microsoft.com/Mail.Send'],
    Type_OutlookTypeAuth::Tp_Application => ['https://graph.microsoft.com/.default']
  ];
  private $authUrl = "https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize";
  private $tokenUrl = "https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token";
  private $baseUrl = [
    Type_OutlookTypeAuth::Tp_Delegated => "https://graph.microsoft.com/v1.0/me/sendMail",
    Type_OutlookTypeAuth::Tp_Application => "https://graph.microsoft.com/v1.0/users/{EMAIL_DO_REMETENTE}/sendMail"
  ];
  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->load->type('tipocomum');

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

  public function setBaseUrl(): void 
  {
    if ($this->authType == Type_OutlookTypeAuth::Tp_Delegated) {
      $this->baseUrl = $this->baseUrl[Type_OutlookTypeAuth::Tp_Delegated];
    } else {
      $this->baseUrl = $this->baseUrl[Type_OutlookTypeAuth::Tp_Application];
    }
  }

  public function getLoginUrl()
  {
      $params = [
          'client_id'     =>  $this->clientId,
          'redirect_uri'  =>  $this->urlloja->link('', '', 'NONSSL', false) . 'microsoftauth',
          'response_type' =>  'code',
          'response_mode' =>  'query',
          'scope'         =>  implode(',', $this->scopes[$this->authType]),
          'state'         =>  'seguranca_csrf',
      ];

      $authUrl = str_replace('{TENANT_ID}', $this->tenantId, $this->authUrl) . '?' . http_build_query($params);

      return $authUrl;
  }

  public function refreshAccessToken(): void 
  {
    if (!$this->config->get('config_mail_protocol') || $this->config->get('config_mail_protocol') != 'outlook') {
      return;
    }
    $this->setHeader(false, 'application/x-www-form-urlencoded');
    try{
      $dataSend = [
        'grant_type'    => 'refresh_token',
        'client_id'     => $this->clientId,
        'client_secret' => $this->clientSecret,
        'refresh_token' => $this->refreshToken,
        'scope' => implode(' ', $this->scopes[$this->authType])
      ];

      $url = str_replace('{TENANT_ID}', $this->tenantId, $this->tokenUrl);
      
      $data = parent::callAPI($url, '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 Outlook: ' . json_encode($data->error, JSON_UNESCAPED_UNICODE));
      }
      
      $this->load->type('tipocomum');
      $this->load->model('setting/setting');

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

  public function getAccessToken($code = null): bool
  {
      $this->setHeader(false, 'application/x-www-form-urlencoded');

      $url = str_replace('{TENANT_ID}', $this->tenantId, $this->tokenUrl);

      $param = [
          "client_id" => $this->clientId,
          "client_secret" => $this->clientSecret,
          "redirect_uri" => $this->urlloja->link('', '', 'NONSSL', false) . 'microsoftauth',
          "scope" => implode(' ', $this->scopes[$this->authType]),
      ];

      if ($this->authType == Type_OutlookTypeAuth::Tp_Delegated) {
          if (!$code) {
              return false;
          }
          $param['grant_type'] = 'authorization_code';
          $param['code'] = $code;
      } else {
          $param['grant_type'] = 'client_credentials';
      }

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

      if ($this->error) {
          return false;
      } else {
          if (is_object($data) && property_exists($data, 'access_token')) {
              $accessToken = $data->access_token;
              $tokenExpire = $data->expires_in;
              $refreshToken = $data->refresh_token ?? null;
              $this->setAccess($accessToken, $refreshToken, $tokenExpire);
          }
          return true;
      }
  }

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

      $data = array(
          'outlook_access_token' => $accessToken,
          'outlook_token_expire' => time() + $tokenExpire,
          'outlook_refresh_token' => $refreshToken,
          'outlook_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->authType == Type_OutlookTypeAuth::Tp_Application) {
          if (!$this->accessToken || $this->tokenExpires < time()) {
            $this->getAccessToken();
          }
          return true;
      } else {
        if (!$this->accessToken || !$this->refreshToken) {
          return false;
        }
        if ($this->tokenExpires < time()) {
          $this->refreshAccessToken();
        }
        return true;
      }
  }

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

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

  private function setCredentials(): void 
  {
    $this->tenantId = $this->config->get('outlook_tenant_id') ?? $this->tenantId;
    $this->clientId = $this->config->get('outlook_client_id') ?? $this->clientId;
    $this->clientSecret = $this->config->get('outlook_client_secret') ? $this->decryptData($this->config->get('outlook_client_secret')) : $this->clientSecret;
    $this->accessToken = $this->config->get('outlook_access_token') ?? $this->accessToken;
    $this->authType = $this->config->get('outlook_type_auth') ?? $this->authType;
    $this->refreshToken = $this->config->get('outlook_refresh_token') ?? $this->refreshToken;
    $this->senderEmail = $this->config->get('outlook_email_sender') ?? $this->senderEmail;
    $this->tokenExpires = $this->config->get('outlook_token_expire') ?? time();
  }

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

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

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

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

      $message = $this->convertMessage($data);

      if (!$message) {
        throw new Exception("Erro ao enviar email Outlook");
      }

      $url = str_replace('{EMAIL_DO_REMETENTE}', $this->senderEmail, $this->baseUrl);
      
      $dataSend = json_encode($message);
      
      $this->setHeader(true);

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

      if ((!$data && $retorno !== 202) || (isset($data->error) && $data->error)) {
        $message = "Erro ao enviar email Outlook";
        if ($retorno == 401 || $retorno == 403) {
          $message = "Usuário não tem permissão para enviar email, consulte sua conta microsoft";
        }
        throw new Exception($message, $retorno ?? 0);
      }
      return true;
    }
    catch (Exception $ex) {
        $error = 'Erro ao enviar email Outlook:';
        $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;
    }

    $message = [
      "message" => [
        "subject" => $infos['subject'],
        "body" => [
          "contentType" => $infos['type'] ?? 'Text',
          "content" => $infos['message']
        ],
        "toRecipients" => []
      ]
    ];

    $infos['to'] = is_array($infos['to']) ? $infos['to'] : [$infos['to']];

    foreach ($infos['to'] as $email) {
      $message['message']['toRecipients'][] = [
        "emailAddress" => [
          "address" => $email
        ]
      ];
    }

    if (isset($infos['cc']) && !empty($infos['cc'])) {
      $infos['cc'] = is_array($infos['cc']) ? $infos['cc'] : [$infos['cc']];
      foreach ($infos['cc'] as $email) {
        $message['message']['ccRecipients'][] = [
          "emailAddress" => [
            "address" => $email
          ]
        ];
      }
    }

    if (isset($infos['attachments']) && !empty($infos['attachments'])) {
      foreach ($infos['attachments'] as $attachment) {
        $message['message']['attachments'][] = [
          "@odata.type" => "#microsoft.graph.fileAttachment",
          "name" => $attachment['name'],
          "contentBytes" => base64_encode($attachment['content'])
        ];
      }
    }

    return $message;
  }

  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){
    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 "";
  }
}
