<?php

class Matera extends RestApi
{
  private $urlFuturaPrd = 'http://matera.futurasistemas.com.br/api/v2/';
  private $urlFuturaTokenPrd = 'http://matera.futurasistemas.com.br/api/v1/';
  private $urlFuturaHml = 'http://grogumateraapi.futurasistemas.com.br/api/v2/';
  private $urlFuturaTokenHml = 'http://grogumateraapi.futurasistemas.com.br/api/v1/';
  private $urlMateraPRD = 'https://mtls-mp.prd.flagship.maas.link/';
  private $urlMateraHML = 'https://mtls-mp.hml.flagship.maas.link/';
  private $certPem = __DIR__ . "/matera/certificate.pem";
  private $keyPem = __DIR__ . "/matera/private_key.key";
  private $registry;
  private $load;
  private $log;
  private $config;
  private $session;
  private $request;
  private $db;
  private $url;

  public function __construct($registry)
  {
    $this->registry = $registry;
    $this->load = $registry->get('load');
    $this->log = $registry->get('log');
    $this->config = $registry->get('config');
    $this->session = $registry->get('session');
    $this->request = $registry->get('request');
    $this->db = $registry->get('db');
    $this->url = $registry->get('url');
  }

  public function getAccessToken()
  {
    $header = [
      "Content-type: application/json; charset=utf-8",
      "Cache-Control: no-cache"
    ];

    try {
      $param_efutura = $this->getParamEfuturaDb();

      $key = Crypto::descriptografarAES256($param_efutura['token_matera'], Crypto::CAES256);

      [$username, $password] = explode('&', $key);

      $environment = $this->config->get('matera_api_ambiente') ?? 'sandbox';

      $urlEnvironment = $environment == 'sandbox' ? $this->urlFuturaTokenHml : $this->urlFuturaTokenPrd;

      $url = "{$urlEnvironment}sys/login?username=$username&password=$password";

      $response = parent::callAPI($url, 'POST', false, $header, false, $retorno, Type_DadosSend::TpDados_Json);

      $token = $response->access_token ?? null;

      return $token;

    } catch (Exception $e) {
      throw new Exception('Erro ao gerar token Matera: ' . $e->getMessage());
    }
    ;
  }

  public function getAccount($data = null)
  {
    $token = $this->getAccessToken();

    $header = [
      "Content-type: application/json; charset=utf-8",
      "Authorization: bearer $token",
      "Cache-Control: no-cache"
    ];

    try {
      $cnpj = $data['cnpj'] ?? preg_replace('/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/', '$1.$2.$3/$4-$5', $data);

      $environment = $this->config->get('matera_api_ambiente') ?? 'sandbox';

      $urlEnvironment = $environment == 'sandbox' ? $this->urlFuturaHml : $this->urlFuturaPrd;

      $response = parent::callAPI("{$urlEnvironment}config/full_account?cnpj=$cnpj&modulo_tipo=2", 'GET', false, $header, false, $retorno, Type_DadosSend::TpDados_Json);

      if ($response && (!isset($response->erros) || !$response->erros)) {
        $fullAccountMatera = $this->getAccountMatera($response->conta);
        $response->billingAddress = $fullAccountMatera['data']['billingAddress'] ?? [];
      }
      ;

      return $response;

    } catch (Exception $e) {
      throw new Exception('Erro ao buscar cliente: ' . $e->getMessage());
    }
    ;
  }

  public function sendPaymentPix($paymentData, $paymentInfo)
  {
    $environment = $this->config->get('matera_api_ambiente') ?? 'sandbox';

    $urlEnvironment = $environment == 'sandbox' ? $this->urlMateraHML : $this->urlMateraPRD;

    $client = $this->getClientDb();

    if ($client) {
      $accountClient = $this->getAccount($client['cnpj']);

      $accountInfo = $accountClient->conta;

      $token = $this->getAuthMatera($accountInfo);

      $transactionHash = $this->getTransactionHash($accountInfo, $paymentInfo);

      $order = $this->createOrder($paymentData, $paymentInfo, $accountClient, 'Pix');

      $header = [
        "Content-type: application/json",
        "Cache-Control: no-cache",
        "Authorization: Bearer $token",
        "Content-Length: " . strlen(json_encode($order)),
        "Transaction-Hash: $transactionHash",
        "Host: " . parse_url($urlEnvironment, PHP_URL_HOST)
      ];

      $auth = [
        "certificate" => $this->certPem,
        "key" => $this->keyPem
      ];

      $data = parent::callAPI("{$urlEnvironment}v1/payments", 'POST', $order, $header, $auth, $retorno, Type_DadosSend::TpDados_Json);

      if ($data) {
        switch ($retorno) {
          case '500':
          case '200':
          case '201':
            return $data;
          default:
            $error = 'Erro no módulo de pagamento';
            $error .= 'Mensagem: Erro na conexão com a Matera';
            throw new Exception($error);
        }
      } else {
        $msgError = "Erro ao finalizar o pedido usando Futura Pix. Erro da API: $data->message";
        throw new Exception($msgError);
      }
    }
  }

  private function createOrder($paymentData, $paymentInfo, $accountClient, $method)
  {
    $this->load->type('tipomatera');

    $externalIdentifier = $this->getExternalIdentifier();

    $accountInfo = $accountClient->conta;

    $payload = [];
    $expireQrcode = $this->config->get('matera_pix');
    $urlCallback = $this->getCallbackUrl($accountInfo->cnpj);

    $data['currency'] = Type_CodigoMoeda::Tp_MoedReal;
    $data['totalAmount'] = floatval($paymentInfo->getParam('amount'));
    $data['externalIdentifier'] = "E*$externalIdentifier";
    $data['callbackAddress'] = $urlCallback;

    $payment_info['transactionType'] = Type_materaTransactionType::Tp_InstantPayment;
    $payment_info['instantPayment']['expiration'] = (int) $expireQrcode['expires_at'] * 24 * 60 * 60;
    $payment_info['instantPayment']['alias'] = $accountInfo->chave_pix;

    $qrCode['errorCorrectionLevel'] = 'M';
    $qrCode['imageWidth'] = 400;
    $qrCode['generateImageRendering'] = false;
    $payment_info['instantPayment']['qrCodeImageGenerationSpecification'] = $qrCode;

    $recipients['account']['accountId'] = $accountInfo->account_id;
    $recipients['amount'] = floatval($paymentInfo->getParam('amount'));
    $recipients['currency'] = Type_CodigoMoeda::Tp_MoedReal;
    $recipients['mediatorFee'] = $paymentData['paymentMediatorFee']['mediatorFee'];
    $recipients['senderComment'] = "";

    $payload = $data;
    $payload['paymentInfo'] = $payment_info;
    $payload['recipients'][] = $recipients;

    return $payload;
  }

  private function getAccountMatera($data = null)
  {
    $this->generateCertificate($data);
    $tokenMatera = $this->getAuthMatera($data);
    $accountId = $data->account_id;
    $secretKey = $data->secret_key;
    $secretKeyDescript = Crypto::descriptografarAES256($secretKey, Crypto::CAES256);
    $transactionHash = hash_hmac('sha256', $accountId, $secretKeyDescript);

    $environment = $this->config->get('matera_api_ambiente') ?? 'sandbox';
    $urlEnvironment = $environment == 'sandbox' ? $this->urlMateraHML : $this->urlMateraPRD;

    $curl = curl_init();

    curl_setopt_array($curl, [
      CURLOPT_URL => "{$urlEnvironment}v1/accounts/{$accountId}",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
        "Content-type: application/json",
        "Cache-Control: no-cache",
        "Authorization: Bearer $tokenMatera",
        "Transaction-Hash: $transactionHash",
        "Host: " . parse_url($urlEnvironment, PHP_URL_HOST)
      ],
      CURLOPT_SSLCERT => $this->certPem,
      CURLOPT_SSLKEY => $this->keyPem,
    ]);

    $response = curl_exec($curl);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    $error = curl_errno($curl) ? curl_error($curl) : null;

    curl_close($curl);

    if ($error) {
      throw new Exception("Erro cURL: {$error}");
    }

    if ($httpCode === 200) {
      $responseData = json_decode($response, true);
    }

    return $responseData;
  }

  private function getCallbackUrl($cnpj = null)
  {
    $urlClient = $this->url->link('payment/matera/callback');

    return $urlClient;
  }

  private function getAuthMatera($data = null)
  {
    $environment = $this->config->get('matera_api_ambiente') ?? 'sandbox';

    $urlEnvironment = $environment == 'sandbox' ? $this->urlMateraHML : $this->urlMateraPRD;

    $client_id = Crypto::descriptografarAES256($data->client_id, Crypto::CAES256);

    $client_secret = Crypto::descriptografarAES256($data->client_secret, Crypto::CAES256);

    $url = "{$urlEnvironment}auth/realms/Matera/protocol/openid-connect/token";

    $body = http_build_query([
      'grant_type' => 'client_credentials',
      'client_id' => $client_id,
      'client_secret' => $client_secret,
    ]);

    $curl = curl_init();

    curl_setopt_array($curl, [
      CURLOPT_URL => $url,
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => $body,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
        'Content-Type: application/x-www-form-urlencoded',
        'Cache-Control: no-cache',
        'Content-Length: ' . strlen($body),
        'Host: ' . parse_url($urlEnvironment, PHP_URL_HOST)
      ],
      CURLOPT_SSLCERT => $this->certPem,
      CURLOPT_SSLKEY => $this->keyPem,
    ]);

    $response = curl_exec($curl);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    $error = curl_errno($curl) ? curl_error($curl) : null;

    curl_close($curl);

    if ($error) {
      throw new Exception("Erro cURL: {$error}");
    }

    if ($httpCode === 200) {
      $responseData = json_decode($response, true);

      if (!isset($responseData['access_token'])) {
        throw new Exception("Erro na autenticação: token não encontrado na resposta.");
      }

      $token = $responseData['access_token'];
      $expiresIn = $responseData['expires_in'] ?? 0;
      $validadeToken = (new DateTime())->modify("+{$expiresIn} seconds");

      return $token;

    } else {
      throw new Exception("Erro HTTP: Código {$httpCode}. Resposta: {$response}");
    }
  }

  private function getClientDb()
  {
    $result = $this->db->query("select * from oc_matera_config");

    $client = $result->row;

    return $client;
  }

  private function getParamEfuturaDb()
  {
    $result = $this->db->query("select * from parametros_efutura");

    $param_efutura = $result->row;

    return $param_efutura;
  }

  private function getTransactionHash($accountInfo = null, $paymentInfo = null)
  {
    $alias = $accountInfo->chave_pix;
    $totalAmount = floor($paymentInfo->getParam('amount'));
    $accountId = $accountInfo->account_id;
    $secretKey = $accountInfo->secret_key;

    $secretKeyDescript = Crypto::descriptografarAES256($secretKey, Crypto::CAES256);

    $hashBase = "{$alias}{$totalAmount}{$accountId}{$totalAmount}";

    $transactionHash = hash_hmac('sha256', $hashBase, $secretKeyDescript);

    return $transactionHash;
  }

  private function getExternalIdentifier()
  {
    $guid = bin2hex(random_bytes(16));

    $guidFormat = str_replace('-', '', $guid);

    return strtoupper(substr($guidFormat, 0, 32));
  }

  public function generateCertificate($data = null)
  {
    if ($data) {
      $privateKey = $data->chave_privada;
      $certificate = $data->certificado;

      $privateKeyDescript = Crypto::descriptografarAES256($privateKey, Crypto::CAES256);
      $certificateDescript = Crypto::descriptografarAES256($certificate, Crypto::CAES256);

      $key = base64_decode($privateKeyDescript);
      $certificate = base64_decode($certificateDescript);

      $directory = DIR_SYSTEM . 'library/matera';
      $keyFile = $directory . '/private_key.key';
      $pemFile = $directory . '/certificate.pem';

      try {
        file_put_contents($keyFile, $key);
        file_put_contents($pemFile, $certificate);
      } catch (Exception $e) {
        $logData = [
          'message' => 'Erro ao criar arquivo',
          'exception' => [
            'type' => get_class($e),
            'message' => $e->getMessage(),
            'code' => $e->getCode(),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString()
          ],
          'datetime' => date('c'), // data/hora no padrão ISO
        ];
        $this->log->write(json_encode($logData, JSON_UNESCAPED_UNICODE));
      }
    }
  }
}
