<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Export\Formatter;

use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\ExportManagerAwareInterface;
use Chill\MainBundle\Export\FormattedExportGeneration;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff

/**
 * Create a CSV List for the export.
 */
class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAwareInterface
{
    use ExportManagerAwareTrait;

    public function __construct(private readonly TranslatorInterface $translator) {}

    /**
     * build a form, which will be used to collect data required for the execution
     * of this formatter.
     *
     * @uses appendAggregatorForm
     */
    public function buildForm(
        FormBuilderInterface $builder,
        string $exportAlias,
        array $aggregatorAliases,
    ): void {
        $builder
            ->add('format', ChoiceType::class, [
                'choices' => [
                    'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods',
                    'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx',
                ],
                'placeholder' => 'Choose the format',
            ])
            ->add('numerotation', ChoiceType::class, [
                'choices' => [
                    'yes' => true,
                    'no' => false,
                ],
                'expanded' => true,
                'multiple' => false,
                'label' => 'Add a number on first column',
            ]);
    }

    public function getNormalizationVersion(): int
    {
        return 1;
    }

    public function normalizeFormData(array $formData): array
    {
        return ['format' => $formData['format'], 'numerotation' => $formData['numerotation']];
    }

    public function denormalizeFormData(array $formData, int $fromVersion): array
    {
        return ['format' => $formData['format'], 'numerotation' => $formData['numerotation']];
    }

    public function getFormDefaultData(array $aggregatorAliases): array
    {
        return ['numerotation' => true, 'format' => 'xlsx'];
    }

    public function getName(): string|TranslatableInterface
    {
        return 'Spreadsheet list formatter (.xlsx, .ods)';
    }

    public function generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, ExportGenerationContext $context): FormattedExportGeneration
    {
        $spreadsheet = new Spreadsheet();
        $worksheet = $spreadsheet->getActiveSheet();
        $cacheLabels = $this->prepareCacheLabels($result, $exportAlias, $exportData);

        $this->prepareHeaders($cacheLabels, $worksheet, $result, $formatterData, $exportAlias, $exportData);

        $i = 1;

        foreach ($result as $row) {
            if (true === $formatterData['numerotation']) {
                $worksheet->setCellValue('A'.($i + 1), (string) $i);
            }

            $a = $formatterData['numerotation'] ? 'B' : 'A';

            foreach ($row as $key => $value) {
                $row = $a.($i + 1);

                $formattedValue = $this->getLabel($cacheLabels, $key, $value, $result, $exportAlias, $exportData);

                if ($formattedValue instanceof \DateTimeInterface) {
                    $worksheet->setCellValue($row, Date::PHPToExcel($formattedValue));

                    if ('000000' === $formattedValue->format('His')) {
                        $worksheet->getStyle($row)
                            ->getNumberFormat()
                            ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
                    } else {
                        $worksheet->getStyle($row)
                            ->getNumberFormat()
                            ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
                    }
                } elseif ($formattedValue instanceof TranslatableInterface) {
                    $worksheet->setCellValue($row, $formattedValue->trans($this->translator));
                } else {
                    $worksheet->setCellValue($row, $formattedValue);
                }
                ++$a;
            }

            ++$i;
        }

        switch ($formatterData['format']) {
            case 'ods':
                $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods');
                $contentType = 'application/vnd.oasis.opendocument.spreadsheet';

                break;

            case 'xlsx':
                $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
                $contentType = 'application/vnd.openxmlformats-officedocument.'
                    .'spreadsheetml.sheet';

                break;

            case 'csv':
                $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv');
                $contentType = 'text/csv';

                break;

            default:
                // this should not happen
                // throw an exception to ensure that the error is catched
                throw new \OutOfBoundsException('The format '.$formatterData['format'].' is not supported');
        }

        $tempfile = \tempnam(\sys_get_temp_dir(), '');
        $writer->save($tempfile);

        $generated = new FormattedExportGeneration(
            file_get_contents($tempfile),
            $contentType,
        );

        // remove the temp file from disk
        \unlink($tempfile);

        return $generated;
    }

    /**
     * Generate a response from the data collected on differents ExportElementInterface.
     *
     * @param mixed[] $result          The result, as given by the ExportInterface
     * @param mixed[] $formatterData   collected from the current form
     * @param string  $exportAlias     the id of the current export
     * @param array   $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
     * @param array   $filtersData     an array containing the filters data. The key are the filters id, and the value are the data
     *
     * @return Response The response to be shown
     */
    public function getResponse(
        $result,
        $formatterData,
        $exportAlias,
        array $exportData,
        array $filtersData,
        array $aggregatorsData,
        ExportGenerationContext $context,
    ) {
        $generated = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context);

        $response = new BinaryFileResponse($generated->content);
        $response->headers->set('Content-Type', $generated->contentType);

        return $response;
    }

    public function getType(): string
    {
        return FormatterInterface::TYPE_LIST;
    }

    /**
     * Give the label corresponding to the given key and value.
     *
     * @return string|\DateTimeInterface|int|float|TranslatableInterface|null
     *
     * @throws \LogicException if the label is not found
     */
    private function getLabel(array $labelsCache, $key, $value, array $result, string $exportAlias, array $exportData)
    {
        if (!\array_key_exists($key, $labelsCache)) {
            throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($labelsCache))));
        }

        return $labelsCache[$key]($value);
    }

    /**
     * Prepare the label cache which will be used by getLabel.
     *
     * @return array The labels cache
     */
    private function prepareCacheLabels(array $result, string $exportAlias, array $exportData): array
    {
        $labelsCache = [];
        $export = $this->getExportManager()->getExport($exportAlias);
        $keys = $export->getQueryKeys($exportData);

        foreach ($keys as $key) {
            // get an array with all values for this key if possible
            $values = \array_map(static function ($v) use ($key) {
                if (!\array_key_exists($key, $v)) {
                    throw new \RuntimeException(sprintf('This key does not exists: %s. Available keys are %s', $key, \implode(', ', \array_keys($v))));
                }

                return $v[$key];
            }, $result);
            // store the label in the labelsCache
            $labelsCache[$key] = $export->getLabels($key, $values, $exportData);
        }

        return $labelsCache;
    }

    /**
     * add the headers to the csv file.
     */
    protected function prepareHeaders(array $labelsCache, Worksheet $worksheet, array $result, array $formatterData, string $exportAlias, array $exportData)
    {
        $keys = $this->getExportManager()->getExport($exportAlias)->getQueryKeys($exportData);
        // we want to keep the order of the first row. So we will iterate on the first row of the results
        $first_row = \count($result) > 0 ? $result[0] : [];
        $header_line = [];

        if (true === $formatterData['numerotation']) {
            $header_line[] = $this->translator->trans('Number');
        }

        foreach ($first_row as $key => $value) {
            $header_line[] = $this->translator->trans(
                $this->getLabel($labelsCache, $key, '_header', $result, $exportAlias, $exportData)
            );
        }

        if (\count($header_line) > 0) {
            $worksheet->fromArray($header_line, null, 'A1');
        }
    }
}
