<?php

namespace Drupal\basket;

use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Url;
use Drupal\views\Views;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Drupal\Core\Render\Markup;

/**
 * {@inheritdoc}
 */
class BasketExport {

  /**
   * Set basket.
   *
   * @var Drupal\basket\Basket
   */
  protected $basket;

  /**
   * @var boolean
   */
  protected $isAllPages;

  protected $allPageTmpFile;

  const per_page = 100;

  /**
   * {@inheritdoc}
   */
  public function __construct() {
    $this->basket = \Drupal::getContainer()->get('Basket');
    $this->isAllPages = $this->basket->getSettings('export_orders', 'config.all_pages');
    if(!empty($this->isAllPages)) {
      $this->allPageTmpFile = \Drupal::service('file_system')->realpath('temporary://orders_'.\Drupal::currentUser()->id().'.csv');
    }
  }

  /**
   * {@inheritdoc}
   * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
   */
  public function run($subType = NULL) {
    $returnVal = [];
    if (!class_exists(Spreadsheet::class)) {
      $returnVal = $this->basket->getError(404);
    }
    if (!\Drupal::currentUser()->hasPermission('basket access_export_order')) {
      $returnVal = $this->basket->getError(403);
    }
    $request = \Drupal::request()->query->all();
    switch ($subType) {
      case'finish':
        if (!empty($request['download'])) {
          $this->downloadExcel();
          exit;
        }
        $downloadUrl = Url::fromRoute('basket.admin.pages', ['page_type' => 'orders-export-finish'], ['query' => ['download' => 1]])->toString();
        $returnVal = [
          '#prefix'       => '<div class="basket_table_wrap" id="export_finish">',
          '#suffix'       => '</div>',
          [
            '#prefix'       => '<div class="b_title">',
            '#suffix'       => '</div>',
            '#markup'       => $this->basket->translate()->t('Export orders'),
          ], [
            '#prefix'       => '<div class="b_content">',
            '#suffix'       => '</div>',
            '#type'         => 'inline_template',
            '#template'     => '<b>{{ title }}</b><br/>{{ text }}',
            '#context'      => [
              'title'         => $this->basket->translate()->t('Export completed successfully.'),
              'text'          => $this->basket->translate()->t('If the automatic download has not started - click on the @link@.', [
                '@link@'        => Markup::create('<a href="' . $downloadUrl . '">' . $this->basket->translate()->t('link') . '</a>'),
              ]),
            ], [
              '#type'         => 'inline_template',
              '#template'     => '<br/><a href="javascript:void(0);" onclick="self.close()" class="download">{{text}}</a>',
              '#context'      => [
                'text'          => $this->basket->translate()->t('Close page'),
              ],
            ],
          ], [
            '#markup'       => Markup::create('<script type="text/javascript">window.location.href="' . $downloadUrl . '";</script>'),
          ],
        ];

        break;

      default:
        $this->dataInfo('clear');
        $operations = [];

        if (!empty(Views::getEnabledViews()['basket'])) {
          if (!empty($export_fields = \Drupal::request()->query->get('export_fields'))) {
            \Drupal::request()->query->set('filter_fields', $export_fields);
          }
          if(!empty($this->isAllPages)) {
            \Drupal::request()->query->set('page', 0);
          }
          $this->operations($operations);
        }

        if (!empty($operations)) {
          $batch = [
            'title'             => t('Export orders', [], ['context' => 'basket']),
            'operations'        => $operations,
            'basket_batch'      => TRUE,
          ];
          batch_set($batch);
          $response = batch_process(Url::fromRoute('basket.admin.pages', ['page_type' => 'orders-export-finish'])->toString());
          $response->send();
        }
        else {
          $returnVal = $this->basket->getError(404);
        }
        break;
    }
    return $returnVal;
  }

  /**
   * {@inheritdoc}
   */
  public function operations(&$operations, $page = 0) {
    $view = Views::getView('basket');
    $view->setItemsPerPage($this::per_page);
    $view->execute('block_1');
    if (!empty($view->result)) {
      $filterFields = \Drupal::request()->query->get('filter_fields');
      foreach ($view->result as $row) {
        if (empty($row->basket_orders_id)) {
          continue;
        }
        $operations[] = [[$this, 'process'], [[
          'orderId'       => $row->basket_orders_id,
          'page'          => $page,
          'filter_fields' => $filterFields
        ]]];
      }
    }
    if(!empty($operations) && !empty($this->isAllPages)) {
      $operations[] = [[$this, 'nextPageRun'], []];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function nextPageRun(&$context) {
    if(isset($context['results']['runPage'])) {

      $page = $context['results']['runPage'] + 1;
      if(!empty($this->isAllPages)) {
        \Drupal::request()->query->set('page', $page);
      }

      if(!empty($context['results']['filterFields'])) {
        \Drupal::request()->query->set('filter_fields', $context['results']['filterFields']);
      }

      $operations = [];
      $this->operations($operations, $page);

      if(!empty($operations)) {
        $batch = [
          'title'             => t('Export orders', [], ['context' => 'basket']),
          'operations'        => $operations,
          'basket_batch'      => TRUE,
        ];
        batch_set($batch);
      }
    }
  }

  /**
   * {@inheritdoc}
   * @throws \Exception
   */
  public function process($info, &$context) {
    if(!empty($this->isAllPages)) {
      $context['results']['runPage'] = $info['page'] ?? 0;
      $context['results']['filterFields'] = $info['filter_fields'] ?? [];

      if(!empty($context['results']['runPage'])) {
        $context['message'] = t('Export orders', [], ['context' => 'basket']).' / '.t('Page').' #'.$context['results']['runPage'];
        $context['message'] .= '<br/>Per page: '.$this::per_page;
      }
    }

    $order = $this->basket->orders($info['orderId'])->load();
    $data = [
      'lines'     => [],
      'color'     => NULL,
    ];
    $context = [];
    foreach ($this->getTokenInfo() as $keyTwig => $tokenTwig) {
      $context[$keyTwig] = $this->basket->token()->getToken($keyTwig, [
        'order'     => $order,
      ]);
    }
    $config = $this->basket->getSettings('export_orders', 'config');
    $orderNode = NULL;
    if (!empty($order->nid)) {
      $orderNode = \Drupal::entityTypeManager()->getStorage('node')->load($order->nid);
    }
    if (empty($order->items)) {
      $order->items = [[]];
    }
    $lineKey = 0;
    foreach ($order->items as $row) {
      if (!empty($lineKey)) {
        $orderNode = NULL;
        $context = [];
      }
      foreach ($this->getTokenInfo() as $keyTwig => $tokenTwig) {
        if (empty($tokenTwig['lineAll'])) {
          continue;
        }
        $context[$keyTwig] = $this->basket->token()->getToken($keyTwig . '.' . $row->id, [
          'orderItem'     => $row,
          'order'         => $order,
        ]);
      }
      $data['lines'][$lineKey] = [];
      foreach (array_filter($config['orders']['data']) as $letter => $tokenText) {
        if (!empty(trim($tokenText))) {
          $tokenText = [
            '#type'     => 'inline_template',
            '#template' => $tokenText,
            '#context'  => $context,
          ];
          $data['lines'][$lineKey][$letter] = trim(\Drupal::token()->replace(
            \Drupal::service('renderer')->render($tokenText), [
            'user'      => !empty($orderNode) ? \Drupal::entityTypeManager()->getStorage('user')->load($orderNode->get('uid')->target_id) : NULL,
            'node'      => $orderNode,
          ], [
              'clear'     => TRUE,
            ]
          ));
        }
        else {
          $data['lines'][$lineKey][$letter] = NULL;
        }
      }
      $lineKey++;
    }

    if(!empty($this->allPageTmpFile)) {
      // Set line data
      $fp = fopen($this->allPageTmpFile, 'a');
      if(!empty($data['lines'])) {
        foreach ($data['lines'] as $line) {
          fputcsv($fp, $line);
        }
      }
    }

    else if (!empty($data['lines'])) {
      \Drupal::database()->insert('basket_orders_export')
        ->fields([
          'uid'       => \Drupal::currentUser()->id(),
          'data'      => serialize($data),
        ])
        ->execute();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getTokenInfo() {
    $info = \Drupal::service('extension.list.module')->getPath('basket') . '/config/basket_install/TwigExcelTokens.yml';
    $tokens = Yaml::decode(file_get_contents($info));

    // Alter
    $templateType = 'orders_export';
    \Drupal::moduleHandler()->alter('basketTemplateTokens', $tokens, $templateType);

    return $tokens;
  }

  /**
   * {@inheritdoc}
   */
  public function dataInfo($type) {
    $return = NULL;
    switch ($type) {
      case'clear':
        \Drupal::database()->delete('basket_orders_export')
          ->condition('uid', \Drupal::currentUser()->id())
          ->execute();
        if(!empty($this->allPageTmpFile)) {

          // Clear data
          file_put_contents($this->allPageTmpFile, '');

          // Set headers
          $headers = [];
          $config = $this->basket->getSettings('export_orders', 'config');
          foreach ($config['orders']['header'] as $text) {
            if (empty(trim($text))) {
              continue;
            }
            $headers[] = $this->basket->translate()->trans(trim($text));
          }
          $fp = fopen($this->allPageTmpFile, 'a');
          fputcsv($fp, $headers);
        }
        break;

      case'load':
        $return = \Drupal::database()->select('basket_orders_export', 'f')
          ->fields('f', ['data'])
          ->condition('f.uid', \Drupal::currentUser()->id())
          ->orderBy('f.id', 'ASC')
          ->execute()->fetchCol();

        break;
    }
    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function getLinkOrderExport($orderId) {
    $context = $this->getLinkOrderExportContext($orderId);
    if (empty($context)) {
      return [];
    }
    return [
      '#type'         => 'inline_template',
      '#template'     => '<a href="{{ url }}" target="_blank" class="button--link"><span class="ico">{{ ico|raw }}</span> {{ text }}</a>',
      '#context'      => $this->getLinkOrderExportContext($orderId),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getLinkOrderExportContext($orderId) {
    if (!\Drupal::currentUser()->hasPermission('basket access_export_order')) {
      return [];
    }
    $order = $this->basket->orders($orderId)->load();
    if (empty($order->id) || !is_numeric($order->id)) {
      return [];
    }
    if (!class_exists(Spreadsheet::class)) {
      return [];
    }
    if (!empty($order->is_delete)) {
      return [];
    }
    return [
      'text'          => $this->basket->translate()->t('Export'),
      'ico'           => $this->basket->getIco('export.svg'),
      'url'           => Url::fromRoute('basket.admin.pages', [
        'page_type'     => 'orders-export',
      ], [
        'query'         => [
          'export_fields' => ['id' => $orderId],
        ],
      ])->toString(),
      'target'        => '_blank',
    ];
  }

  /**
   * {@inheritdoc}
   * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
   * @throws \PhpOffice\PhpSpreadsheet\Exception
   */
  private function downloadExcel() {
    if(!empty($this->allPageTmpFile)) {
      $response = new BinaryFileResponse($this->allPageTmpFile, 200, [], FALSE, 'attachment');
      $response->deleteFileAfterSend(TRUE);
      $response->send();
      exit;
    }
    $config = $this->basket->getSettings('export_orders', 'config');
    $filename = $this->basket->translate()->t('Orders') . ' (' . date('d.m.Y H:i') . ').xlsx';
    $file_path = \Drupal::service('file_system')->realpath('temporary://'.$filename);

    // Spreadsheet
    $spreadsheet = new Spreadsheet();
    $spreadsheet->getActiveSheet()->getPageSetup()->setRowsToRepeatAtTopByStartAndEnd(1, 1);
    $sheet = $spreadsheet->getActiveSheet();

    // Header
    $headerNum = 1;
    foreach ($config['orders']['header'] as $letter => $text) {
      if (empty(trim($text))) {
        continue;
      }
      $sheet->setCellValue($letter . '' . $headerNum, $this->basket->translate()->trans(trim($text)));
    }

    // Fixed header
    $sheet->freezePane('A2');

    // Rows
    $rowsNum = $headerNum + 1;
    $rows = self::dataInfo('load');
    $rowBorders = [];
    if (!empty($rows)) {
      foreach ($rows as $row) {
        $row = unserialize($row);
        if (empty($row['lines'])) {
          continue;
        }
        foreach ($row['lines'] as $line) {
          foreach ($line as $letter => $setValue) {
            if (empty(trim($setValue))) {
              continue;
            }
            $sheet->setCellValueExplicit($letter . '' . $rowsNum, trim($setValue), DataType::TYPE_STRING);
          }
          $rowsNum++;
        }
        $rowBorders[($rowsNum - 1)] = ($rowsNum - 1);
      }
    }
    // Colors
    $highestColumn = $sheet->getHighestColumn();

    // Header
    $spreadsheet->getActiveSheet()->getStyle('A' . $headerNum . ':' . $highestColumn . $headerNum)->applyFromArray([
      'font' => [
        'bold' => TRUE,
      ],
      'fill'      => [
        'fillType'  => Fill::FILL_SOLID,
        'color'     => [
          'rgb'       => '0569CC',
        ],
      ],
      'borders' => [
        'allBorders' => [
          'borderStyle' => Border::BORDER_THIN,
          'color' => ['argb' => '000000'],
        ],
      ],
    ]);
    $spreadsheet->getActiveSheet()->getStyle('A' . $headerNum . ':' . $highestColumn . $headerNum)->getFont()->getColor()->setARGB('ffffff');

    // Rows
    if (!empty($rowBorders)) {
      foreach ($rowBorders as $rowBorderNum) {
        $spreadsheet->getActiveSheet()->getStyle('A' . $rowBorderNum . ':' . $highestColumn . $rowBorderNum)->applyFromArray([
          'borders' => [
            'bottom' => [
              'borderStyle' => Border::BORDER_MEDIUM,
              'color' => ['argb' => '000000'],
            ],
          ],
        ]);
      }
    }
    $spreadsheet->getActiveSheet()->getStyle('A' . ($headerNum + 1) . ':' . $highestColumn . $rowsNum)->getAlignment()->setWrapText(TRUE);

    // Auto width
    foreach (range('A', $highestColumn) as $letter) {
      $spreadsheet->getActiveSheet()->getColumnDimension($letter)->setAutoSize(TRUE);
    }

    // Save
    $writer = new Xlsx($spreadsheet);
    $writer->save($file_path);

    // Download
    $response = new BinaryFileResponse($file_path, 200, [], FALSE, 'attachment');
    $response->deleteFileAfterSend(TRUE);
    $response->send();
  }

}
