<?php
namespace shop;

class Order implements \ArrayAccess
{
  private const APILO_SYNC_QUEUE_FILE = '/temp/apilo-sync-queue.json';

  public $id;
  public $products;
  public $statuses;
  public $status;
  public $client_email;
  public $summary;
  public $apilo_order_id;
  public $date_order;
  public $payment_method_id;
  public $transport_cost;
  public $number;

  public function __construct( int $order_id, $hash = '', $przelewy24_hash = '' )
  {
    global $mdb;

    if ( $order_id )
    {
      $result = $mdb -> get( 'pp_shop_orders', '*', [ 'id' => $order_id ] );
      if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val )
        $this -> $key = $val;
    }

    if ( $hash )
    {
      $result = $mdb -> get( 'pp_shop_orders', '*', [ 'hash' => $hash ] );
      if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val )
        $this -> $key = $val;
    }

    if ( $przelewy24_hash )
    {
      $result = $mdb -> get( 'pp_shop_orders', '*', [ 'przelewy24_hash' => $przelewy24_hash ] );
      if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val )
        $this -> $key = $val;
    }

    $this -> products = $mdb -> select( 'pp_shop_order_products', '*', [ 'order_id' => $order_id ] );
    $this -> statuses = $mdb -> select( 'pp_shop_order_statuses', '*', [ 'order_id' => $order_id, 'ORDER' => [ 'id' => 'DESC' ] ] );
  }

  static public function notes_save( int $order_id, $notes )
  {
    global $mdb;
    return $mdb -> update( 'pp_shop_orders', [ 'notes' => $notes ], [ 'id' => $order_id ] );
  }

  public static function order_statuses()
  {
    global $mdb;

    $repository = new \Domain\ShopStatus\ShopStatusRepository( $mdb );
    return $repository -> allStatuses();
  }

  public function update_aplio_order_status_date( $date )
  {
    global $mdb;
    return $mdb -> update( 'pp_shop_orders', [ 'apilo_order_status_date' => $date ], [ 'id' => $this -> id ] );
  }

  // set_as_unpaid
  public function set_as_unpaid() {
    global $mdb;

    $mdb -> update( 'pp_shop_orders', [ 'paid' => 0 ], [ 'id' => $this -> id ] );
    return true;
  }

  // set_as_paid
  public function set_as_paid( $send_email = false )
  {
    global $mdb, $config;

    $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );

    // apilo
    $apilo_settings = $integrationsRepository -> getSettings( 'apilo' );
    if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and $apilo_settings['sync_orders'] )
    {
      // put data to file
      if ( $config['debug']['apilo'] )
      {
        file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', date( 'Y-m-d H:i:s' ) . " --- SET AS PAID\n\n", FILE_APPEND );
        file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND );
      }

      if ( $this -> apilo_order_id and !$this -> sync_apilo_payment() )
      {
        self::queue_apilo_sync( (int)$this -> id, true, null, 'payment_sync_failed' );
      }
    }

    $mdb -> update( 'pp_shop_orders', [ 'paid' => 1 ], [ 'id' => $this -> id ] );
    $this -> update_status( 1, $send_email );

    $dir_logs = $_SERVER['DOCUMENT_ROOT'] . '/logs/';
    if ( !is_dir( $dir_logs ) )
      mkdir( $dir_logs, 0777, true );

    // $file = $dir_logs . date( 'Y-m-d' ) . '-order-set-as-paid.txt';
    // $content = date( 'Y-m-d H:i:s' ) . ' | ' . $this -> id . ' | ' . $this -> number . ' | ' . $this -> summary . ' | ' . $this -> client_email . "\n";
    // $content .= print_r( $apilo_response, true ) . "\n\n";
    // file_put_contents( $file, $content, FILE_APPEND );

    return true;
  }

  // zmiana statusu zamówienia
  public function update_status( int $status, int $email_send )
  {
    global $mdb, $config;

    $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );

    if ( $this -> status == $status )
      return false;

    if ( $mdb -> update( 'pp_shop_orders', [ 'status' => $status ], [ 'id' => $this -> id ] ) )
    {
      $mdb -> insert( 'pp_shop_order_statuses', [
        'order_id' => $this -> id,
        'status_id' => $status,
        'mail' => $email_send
      ] );

      if ( $email_send )
      {
        $this -> status = $status;
        $response['email'] = $this -> send_status_change_email();
      }

      $response['result'] = true;

      // apilo
      $apilo_settings = $integrationsRepository -> getSettings( 'apilo' );
      if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and $apilo_settings['sync_orders'] )
      {
        // put data to file
        if ( $config['debug']['apilo'] )
        {
          file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', date( 'Y-m-d H:i:s' ) . " --- UPDATE STATUS\n\n", FILE_APPEND );
          file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND );
        }

        if ( $this -> apilo_order_id and !$this -> sync_apilo_status( (int)$status ) )
        {
          self::queue_apilo_sync( (int)$this -> id, false, (int)$status, 'status_sync_failed' );
        }
      }

      return $response;
    }
    return false;
  }

  // ponowne wysłanie maila o złożonym zamówieniu
  public function order_resend_confirmation_email()
  {
    global $settings;

    $order = \front\factory\ShopOrder::order_details( $this -> id );
    $coupon = (int)$order['coupon_id'] ? new \shop\Coupon( (int)$order['coupon_id'] ) : null;

    $mail_order = \Shared\Tpl\Tpl::view( 'shop-order/mail-summary', [
      'settings' => $settings,
      'order' => $order,
      'coupon' => $coupon,
      ] );

    $settings[ 'ssl' ] ? $base = 'https' : $base = 'http';

    $regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
    $mail_order = preg_replace( $regex, "$1" . $base . "://" . $_SERVER[ 'SERVER_NAME' ] . "$2$4", $mail_order );

    $regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
    $mail_order = preg_replace( $regex, "$1" . $base . "://" . $_SERVER[ 'SERVER_NAME' ] . "$2$4", $mail_order );

    \Shared\Helpers\Helpers::send_email( $this -> client_email, \Shared\Helpers\Helpers::lang( 'potwierdzenie-zamowienia-ze-sklepu' ) . ' ' . $settings[ 'firm_name' ], $mail_order );
    \Shared\Helpers\Helpers::send_email( $settings[ 'contact_email' ], 'Nowe zamówienie / ' . $settings[ 'firm_name' ] . ' / ' . $order['number'] . ' - ' . $order['client_surname'] . ' ' . $order['client_name'], $mail_order );

    return true;
  }

  // wysłanie maila o zmianie statusu
  public function send_status_change_email()
  {
    if ( !$this -> client_email )
      return false;

    $order_statuses = self::order_statuses();

    $firm_name = ( new \Domain\Settings\SettingsRepository( $mdb ) )->getSingleValue( 'firm_name' );

    switch( $this -> status ):
      case 0:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało złożone' );
      break;
      case 1:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało opłacone' );
      break;
      case 2:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - płatność za zamówienie [NUMER] została odrzucona' );
      break;
      case 3:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - płatność za zamówienie [NUMER] jest sprawdzania ręcznie' );
      break;
      case 4:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało przyjęte do realizacji' );
      break;
      case 5:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało wysłane' );
      break;
      case 6:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało zrealizowane' );
      break;
      case 7:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało przygotowane go wysłania' );
      break;
      case 8:
        $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało anulowane' );
      break;
    endswitch;

    $email = new \Email( 0 );
    $email -> load_by_name( '#sklep-zmiana-statusu-zamowienia' );

    $email -> text = str_replace( '[NUMER_ZAMOWIENIA]', $this -> number, $email -> text );
    $email -> text = str_replace( '[DATA_ZAMOWIENIA]', date( 'Y/m/d', strtotime( $this -> date_order ) ), $email -> text );
    $email -> text = str_replace( '[STATUS]', $order_statuses[ $this -> status ], $email -> text );

    return $email -> send( $this -> client_email, $subject, true );
  }

  // wyliczanie wartości zamówienia podczas edycji zamówienia
  static public function calculate_order_summary_by_admin( int $order_id )
  {
    global $mdb;

    $rows = $mdb -> select( 'pp_shop_order_products', '*', [ 'order_id' => $order_id ] );
    if ( \Shared\Helpers\Helpers::is_array_fix( $rows ) ) foreach ( $rows as $row )
    {
      if ( $row['price_brutto_promo'] )
        $summary += $row['price_brutto_promo'] * $row['quantity'];
      else
        $summary += $row['price_brutto'] * $row['quantity'];
    }

    return $summary + $mdb -> get( 'pp_shop_orders', 'transport_cost', [ 'id' => $order_id ] );
  }

  // ADMIN - usunięcie zamówienia
  static public function order_delete( int $order_id )
  {
    global $mdb;
    return $mdb -> delete( 'pp_shop_orders', [ 'id' => $order_id ] );
  }

  // ADMIN - zmiana zamówienia
  static public function order_save_by_admin( int $order_id, string $client_name, string $client_surname, string $client_street, string $client_postal_code, string $client_city, string $client_email, $firm_name = '', $firm_street = '', $firm_postal_code = '', $firm_city = '', $firm_nip = '',
    int $transport_id, string $inpost_paczkomat, int $payment_method_id )
  {
    global $mdb, $user;

    $mdb -> update( 'pp_shop_orders', [
              'client_name' => $client_name,
              'client_surname' => $client_surname,
              'client_street' => $client_street,
              'client_postal_code' => $client_postal_code,
              'client_city' => $client_city,
              'client_email' => $client_email,
              'firm_name' => $firm_name ? $firm_name : null,
              'firm_street' => $firm_street ? $firm_street : null,
              'firm_postal_code' => $firm_postal_code ? $firm_postal_code : null,
              'firm_city' => $firm_city ? $firm_city : null,
              'firm_nip' => $firm_nip ? $firm_nip : null,
              'transport_id' =>  $transport_id,
              'transport' => $mdb -> get( 'pp_shop_transports', 'name_visible', [ 'id' => $transport_id ] ),
              'transport_cost' => $mdb -> get( 'pp_shop_transports', 'cost', [ 'id' => $transport_id ] ),
              'transport_description' => $mdb -> get( 'pp_shop_transports', 'description', [ 'id' => $transport_id ] ),
              'inpost_paczkomat' => $inpost_paczkomat,
              'payment_method_id' => $payment_method_id,
              'payment_method' => $mdb -> get( 'pp_shop_payment_methods', 'name', [ 'id' => $payment_method_id ] ),
            ], [
              'id' => $order_id
            ] );

    $mdb -> update( 'pp_shop_orders', [
              'summary' => \shop\Order::calculate_order_summary_by_admin( $order_id )
            ], [
              'id' => $order_id
            ] );

    return true;
  }

  public function offsetExists( $offset )
  {
    return isset( $this -> $offset );
  }

  public function offsetGet( $offset )
  {
    return $this -> $offset;
  }

  public function offsetSet( $offset, $value )
  {
    $this -> $offset = $value;
  }

  public function offsetUnset( $offset )
  {
    unset( $this -> $offset );
  }

  private function sync_apilo_payment(): bool
  {
    global $config, $mdb;

    $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );

    if ( !(int)$this -> apilo_order_id )
      return true;

    $payment_type = (int)\front\factory\ShopPaymentMethod::get_apilo_payment_method_id( (int)$this -> payment_method_id );
    if ( $payment_type <= 0 )
      $payment_type = 1;

    $payment_date = new \DateTime( $this -> date_order );
    $access_token = $integrationsRepository -> apiloGetAccessToken();

    $ch = curl_init();
    curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' );
    curl_setopt( $ch, CURLOPT_POST, 1 );
    curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
      'amount' => str_replace( ',', '.', $this -> summary ),
      'paymentDate' => $payment_date -> format('Y-m-d\TH:i:s\Z'),
      'type' => $payment_type
    ] ) );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, [
      "Authorization: Bearer " . $access_token,
      "Accept: application/json",
      "Content-Type: application/json"
    ] );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 );
    curl_setopt( $ch, CURLOPT_TIMEOUT, 15 );
    $apilo_response = curl_exec( $ch );
    $http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $curl_error = curl_errno( $ch ) ? curl_error( $ch ) : '';
    curl_close( $ch );

    if ( $config['debug']['apilo'] )
    {
      self::append_apilo_log( "PAYMENT RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_response, true ) . "\n" );
    }

    if ( $curl_error !== '' )
      return false;

    if ( $http_code < 200 or $http_code >= 300 )
      return false;

    return true;
  }

  private function sync_apilo_status( int $status ): bool
  {
    global $config, $mdb;

    $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );

    if ( !(int)$this -> apilo_order_id )
      return true;

    $access_token = $integrationsRepository -> apiloGetAccessToken();

    $ch = curl_init();
    curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' );
    curl_setopt( $ch, CURLOPT_POST, 1 );
    curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
      'id' => $this -> apilo_order_id,
      'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id( $status )
    ] ) );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, [
      "Authorization: Bearer " . $access_token,
      "Accept: application/json",
      "Content-Type: application/json"
    ] );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 );
    curl_setopt( $ch, CURLOPT_TIMEOUT, 15 );
    $apilo_result = curl_exec( $ch );
    $http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $curl_error = curl_errno( $ch ) ? curl_error( $ch ) : '';
    curl_close( $ch );

    if ( $config['debug']['apilo'] )
    {
      self::append_apilo_log( "STATUS RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_result, true ) . "\n" );
    }

    if ( $curl_error !== '' )
      return false;

    if ( $http_code < 200 or $http_code >= 300 )
      return false;

    return true;
  }

  public static function process_apilo_sync_queue( int $limit = 10 ): int
  {
    $queue = self::load_apilo_sync_queue();
    if ( !\Shared\Helpers\Helpers::is_array_fix( $queue ) )
      return 0;

    $processed = 0;

    foreach ( $queue as $key => $task )
    {
      if ( $processed >= $limit )
        break;

      $order_id = (int)( $task['order_id'] ?? 0 );
      if ( $order_id <= 0 )
      {
        unset( $queue[$key] );
        continue;
      }

      $order = new self( $order_id );
      if ( !(int)$order -> id )
      {
        unset( $queue[$key] );
        continue;
      }

      $error = '';
      $sync_failed = false;

      $payment_pending = !empty( $task['payment'] ) and (int)$order -> paid === 1;
      if ( $payment_pending and (int)$order -> apilo_order_id )
      {
        if ( !$order -> sync_apilo_payment() )
        {
          $sync_failed = true;
          $error = 'payment_sync_failed';
        }
      }

      $status_pending = isset( $task['status'] ) and $task['status'] !== null and $task['status'] !== '';
      if ( !$sync_failed and $status_pending and (int)$order -> apilo_order_id )
      {
        if ( !$order -> sync_apilo_status( (int)$task['status'] ) )
        {
          $sync_failed = true;
          $error = 'status_sync_failed';
        }
      }

      if ( $sync_failed )
      {
        $task['attempts'] = (int)( $task['attempts'] ?? 0 ) + 1;
        $task['last_error'] = $error;
        $task['updated_at'] = date( 'Y-m-d H:i:s' );
        $queue[$key] = $task;
      }
      else
      {
        unset( $queue[$key] );
      }

      $processed++;
    }

    self::save_apilo_sync_queue( $queue );

    return $processed;
  }

  private static function queue_apilo_sync( int $order_id, bool $payment, ?int $status, string $error ): void
  {
    if ( $order_id <= 0 )
      return;

    $queue = self::load_apilo_sync_queue();
    $key = (string)$order_id;
    $row = is_array( $queue[$key] ?? null ) ? $queue[$key] : [];

    $row['order_id'] = $order_id;
    $row['payment'] = !empty( $row['payment'] ) || $payment ? 1 : 0;
    if ( $status !== null )
      $row['status'] = $status;

    $row['attempts'] = (int)( $row['attempts'] ?? 0 ) + 1;
    $row['last_error'] = $error;
    $row['updated_at'] = date( 'Y-m-d H:i:s' );

    $queue[$key] = $row;
    self::save_apilo_sync_queue( $queue );
  }

  private static function apilo_sync_queue_path(): string
  {
    return dirname( __DIR__, 2 ) . self::APILO_SYNC_QUEUE_FILE;
  }

  private static function load_apilo_sync_queue(): array
  {
    $path = self::apilo_sync_queue_path();
    if ( !file_exists( $path ) )
      return [];

    $content = file_get_contents( $path );
    if ( !$content )
      return [];

    $decoded = json_decode( $content, true );
    if ( !is_array( $decoded ) )
      return [];

    return $decoded;
  }

  private static function save_apilo_sync_queue( array $queue ): void
  {
    $path = self::apilo_sync_queue_path();
    $dir = dirname( $path );
    if ( !is_dir( $dir ) )
      mkdir( $dir, 0777, true );

    file_put_contents( $path, json_encode( $queue, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOCK_EX );
  }

  private static function append_apilo_log( string $message ): void
  {
    $base = isset( $_SERVER['DOCUMENT_ROOT'] ) && $_SERVER['DOCUMENT_ROOT']
      ? rtrim( $_SERVER['DOCUMENT_ROOT'], '/\\' )
      : dirname( __DIR__, 2 );

    $dir = $base . '/logs';
    if ( !is_dir( $dir ) )
      mkdir( $dir, 0777, true );

    file_put_contents(
      $dir . '/apilo.txt',
      date( 'Y-m-d H:i:s' ) . ' --- ' . $message . "\n\n",
      FILE_APPEND
    );
  }
}
