<?php

namespace GeminiLabs\SiteReviews\Database;

use GeminiLabs\League\Csv\Reader;
use GeminiLabs\League\Csv\Statement;
use GeminiLabs\League\Csv\TabularDataReader;
use GeminiLabs\SiteReviews\Commands\CreateReview;
use GeminiLabs\SiteReviews\Database;
use GeminiLabs\SiteReviews\Database\Tables\TableTmp;
use GeminiLabs\SiteReviews\Defaults\ImportResultDefaults;
use GeminiLabs\SiteReviews\Helpers\Arr;
use GeminiLabs\SiteReviews\Request;
use GeminiLabs\SiteReviews\Review;

class ImportManager
{
    /**
     * The lock is re-armed by every import page request, so it lapses
     * only when a run dies between requests.
     */
    public const LOCK_EXPIRATION = 120;

    public const LOOKUP_CHUNK = 50;

    public function flush(): void
    {
        glsr(TableTmp::class)->drop();
    }

    public function import(int $limit = 1, int $offset = 0): array
    {
        $this->markImporting();
        $this->lock();
        set_time_limit(0);
        wp_raise_memory_limit('admin');
        wp_defer_term_counting(true);
        wp_suspend_cache_invalidation(true);
        $result = glsr(ImportResultDefaults::class)->defaults();
        $buffer = [];
        foreach ($this->records($limit, $offset) as $values) {
            $buffer[] = $values;
            if (static::LOOKUP_CHUNK === count($buffer)) {
                $this->importRecords($buffer, $result);
                $buffer = [];
            }
        }
        if (!empty($buffer)) {
            $this->importRecords($buffer, $result);
        }
        wp_defer_term_counting(false);
        wp_suspend_cache_invalidation(false);
        return glsr(ImportResultDefaults::class)->restrict($result);
    }

    public function importAttachments(int $limit = 1, int $offset = 0): array
    {
        $this->markImporting();
        $this->lock();
        set_time_limit(0);
        wp_raise_memory_limit('image');
        wp_suspend_cache_invalidation(true);
        $limit = max(1, $limit);
        $offset = max(0, $offset);
        $result = glsr()->filterArray('import/reviews/attachments', [], $limit, $offset);
        wp_suspend_cache_invalidation(false);
        return glsr(ImportResultDefaults::class)->restrict($result);
    }

    public function importedReview(string $submittedHash): ?Review
    {
        $reviewIds = $this->importedReviewIds([$submittedHash]);
        if (empty($reviewIds[$submittedHash])) {
            return null;
        }
        $review = glsr_get_review($reviewIds[$submittedHash]);
        if (!$review->isValid()) {
            return null;
        }
        return $review;
    }

    /**
     * Returns [submitted_hash => review_id] for the hashes that match an
     * imported review. The oldest review wins when several share a hash.
     */
    public function importedReviewIds(array $submittedHashes): array
    {
        if (empty($submittedHashes)) {
            return [];
        }
        $hashes = implode("','", array_map('esc_sql', $submittedHashes));
        $sql = "
            SELECT pm.meta_value AS submitted_hash, MIN(p.ID) AS review_id
            FROM table|posts AS p
            INNER JOIN table|postmeta AS pm ON (pm.post_id = p.ID)
            WHERE 1=1
            AND p.post_type = %s
            AND pm.meta_key = '_submitted_hash'
            AND pm.meta_value IN ('{$hashes}')
            GROUP BY pm.meta_value
        ";
        $sql = glsr(Query::class)->sql($sql, glsr()->post_type);
        $results = glsr(Database::class)->dbGetResults($sql, \ARRAY_A);
        return array_column(Arr::consolidate($results), 'review_id', 'submitted_hash');
    }

    public function lock(): void
    {
        set_transient(glsr()->prefix.'import_lock', time(), static::LOCK_EXPIRATION);
    }

    public function locked(): bool
    {
        return false !== get_transient(glsr()->prefix.'import_lock');
    }

    public function markImporting(): void
    {
        if (!defined('WP_IMPORTING')) {
            define('WP_IMPORTING', true);
        }
    }

    public function prepare(): void
    {
        glsr(TableTmp::class)->create(); // create a temporary table for importing
    }

    public function records(int $limit = 1, int $offset = 0): TabularDataReader
    {
        $this->prepare();
        $reader = Reader::from($this->tempFilePath());
        $reader->setHeaderOffset(0);
        return (new Statement())
            ->offset(max(0, $offset))
            ->limit(max(1, $limit))
            ->process($reader);
    }

    public function tempFilePath(): string
    {
        $uploads = wp_upload_dir();
        if (!file_exists($uploads['basedir'])) {
            $uploads = wp_upload_dir(null, true, true); // maybe the site has been moved, so refresh the cached uploads path
        }
        $path = trailingslashit($uploads['basedir']);
        $path = trailingslashit($path.glsr()->id);
        $path = trailingslashit($path.'temp');
        wp_mkdir_p($path);
        return $path.'import.csv';
    }

    public function unlinkTempFile(): bool
    {
        $path = $this->tempFilePath();
        if (!is_file($path)) {
            return false;
        }
        unlink($path); // delete the temporary import file
        return true;
    }

    public function unlock(): void
    {
        delete_transient(glsr()->prefix.'import_lock');
    }

    protected function importRecords(array $records, array &$result): void
    {
        $items = [];
        foreach ($records as $values) {
            $request = new Request($values);
            $command = new CreateReview($request);
            $submitted = $command->submitted();
            if (empty($submitted['submitted'])) {
                ++$result['skipped'];
                continue;
            }
            $items[] = [$request, $command, $submitted['submitted_hash']];
        }
        $reviewIds = $this->importedReviewIds(array_unique(array_column($items, 2)));
        foreach ($items as [$request, $command, $hash]) {
            $review = empty($reviewIds[$hash]) ? null : glsr_get_review($reviewIds[$hash]);
            if ($review?->isValid()) {
                $result['attachments'] += glsr()->filterInt('import/review/attachments', 0, $request, $review, false);
                ++$result['skipped'];
                continue;
            }
            if ($review = glsr(ReviewManager::class)->create($command)) {
                $reviewIds[$hash] = $review->ID; // a twin later in this chunk must match this review
                $result['attachments'] += glsr()->filterInt('import/review/attachments', 0, $request, $review, true);
                ++$result['imported'];
                continue;
            }
            ++$result['skipped'];
        }
    }
}
