<?php

namespace GeminiLabs\SiteReviews\Commands;

use GeminiLabs\League\Csv\CannotInsertRecord;
use GeminiLabs\League\Csv\EscapeFormula;
use GeminiLabs\League\Csv\Writer;
use GeminiLabs\SiteReviews\Arguments;
use GeminiLabs\SiteReviews\Database\ExportManager;
use GeminiLabs\SiteReviews\Defaults\AdditionalFieldsDefaults;
use GeminiLabs\SiteReviews\Defaults\StatDefaults;
use GeminiLabs\SiteReviews\Helpers\Arr;
use GeminiLabs\SiteReviews\Helpers\Str;
use GeminiLabs\SiteReviews\Modules\Notice;
use GeminiLabs\SiteReviews\Request;

class ExportReviews extends AbstractCommand
{
    protected Request $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function handle(): void
    {
        if (!glsr()->hasPermission('tools', 'general')) {
            glsr(Notice::class)->addError(
                _x('You do not have permission to export reviews.', 'admin-text', 'site-reviews')
            );
            $this->fail();
            return;
        }
        try {
            $records = $this->fetchReviews();
            $firstRecord = $records->current();
            if (empty($firstRecord)) {
                glsr(Notice::class)->addWarning(_x('No reviews were found to export.', 'admin-text', 'site-reviews'));
                $this->fail();
                return;
            }
            nocache_headers();
            $filename = sprintf('%s_%s.csv', date('YmdHi'), glsr()->id);
            $writer = $this->writer();
            $writer->insertOne(array_keys($firstRecord));
            $writer->insertAll($records);
            $writer->output($filename);
            glsr_exit();
        } catch (CannotInsertRecord $e) {
            $this->fail();
            glsr(Notice::class)->addError($e->getMessage());
            glsr_log()
                ->warning('Unable to insert row into CSV export file')
                ->debug($e->getRecord());
        }
    }

    protected function fetchReviews(): \Generator
    {
        $args = [
            'assigned_posts' => $this->request->assigned_posts,
            'assigned_terms' => $this->request->assigned_terms,
            'assigned_users' => $this->request->assigned_users,
            'author_id' => $this->request->author_id,
            'date' => $this->request->cast('date', 'date'),
            'limit' => 500,
            'post_status' => $this->request->post_status,
        ];
        $args = glsr()->filterArray('export/review/args', $args, $this->request);
        $args = glsr()->args($args);
        $header = [];
        $postId = 0;
        while (true) {
            $reviews = glsr(ExportManager::class)->export($args->merge([
                'post_id' => $postId,
            ]));
            if (empty($reviews)) {
                break;
            }
            if (empty($header)) {
                $header = $this->headerValues($args, $reviews[0]);
            }
            foreach ($reviews as $review) {
                $meta = $this->postMeta($review['ID']);
                $meta = glsr()->filterArray('export/review/meta', $meta, $args, $review);
                $custom = wp_parse_args($meta, array_fill_keys($header, ''));
                $record = wp_parse_args($review, $custom);
                unset($record['ID']);
                yield $record;
            }
            $postId = end($reviews)['ID'];
        }
    }

    protected function headerValues(Arguments $args, array $record): array
    {
        $additionalHeader = array_keys(glsr(AdditionalFieldsDefaults::class)->defaults());
        $customHeader = glsr(ExportManager::class)->customHeader($args);
        $header = array_merge(array_keys($record), $additionalHeader, $customHeader);
        $header = glsr()->filterArray('export/review/header', $header, $record);
        return $header;
    }

    protected function postMeta(int $postId): array
    {
        $meta = get_post_meta($postId);
        if (!is_array($meta)) {
            return [];
        }
        $additionalKeys = array_keys(glsr(AdditionalFieldsDefaults::class)->defaults());
        $additionalKeys = array_map(fn ($key) => Str::prefix($key, '_'), $additionalKeys);
        $meta = array_map(fn ($val) => $val[0] ?? '', $meta);
        $meta = array_filter($meta, function ($key) use ($additionalKeys) {
            return str_starts_with($key, '_custom_') || in_array($key, $additionalKeys);
        }, ARRAY_FILTER_USE_KEY);
        $results = [];
        foreach ($meta as $key => $value) {
            $key = Str::removePrefix($key, '_');
            if ('geolocation' !== $key) {
                $results[$key] = $value;
                continue;
            }
            // geolocation values need their own columns
            $geolocation = glsr(StatDefaults::class)->restrict(
                Arr::consolidate(maybe_unserialize($value))
            );
            unset($geolocation['rating_id']);
            foreach (array_filter($geolocation) as $suffix => $location) {
                $results["{$key}_{$suffix}"] = $location;
            }
        }
        return $results;
    }

    protected function writer(): Writer
    {
        $writer = Writer::createFromString('');
        $writer->addFormatter(new EscapeFormula());
        return $writer;
    }
}
