<?php
declare(strict_types=1);

namespace Celena\Plugin\Shop\Admin;

use Celena\Core\Database\Connection;
use Celena\Core\Http\Request;
use Celena\Core\Http\Response;
use Celena\Core\Security\Auth;
use Celena\Core\Security\Csrf;
use Celena\Core\Security\Filter;
use Celena\Core\Template\Engine;
use Celena\Module\AdminBase\AdminController;
use Celena\Module\Settings\Options;

/**
 * Импорт/экспорт каталога через CSV. Обмен через Excel-совместимые колонки:
 *   id, slug, title, sku, barcode, price, old_price, stock, status,
 *   is_featured, manufacturer (slug), category (slug), short, body,
 *   seo_title, seo_description, cover.
 *
 * При импорте:
 *   - если есть id и товар существует — UPDATE по id;
 *   - иначе — UPSERT по slug (если slug пустой, генерируется из title);
 *   - manufacturer/category матчатся по slug; если не найдены — null.
 *
 * Загрузка файла должна укладываться в php.ini upload_max_filesize.
 * Большие выгрузки лучше планировать через очередь (планируется в v2).
 */
final class CsvAdminController extends AdminController
{
    private function tr(): \Celena\Core\I18n\Translator
    {
        return \Celena\Core\Kernel::container()->make(\Celena\Core\I18n\Translator::class);
    }

    public function __construct(
        Engine $engine,
        Auth $auth,
        Options $options,
        private readonly Connection $conn,
    ) {
        parent::__construct($engine, $auth, $options);
    }

    public function importForm(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        return $this->view('plugins/shop/admin/csv_import', [], 'shop-csv', $this->tr()->translate('shop.csv.import_title'));
    }

    public function importRun(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/products/import', $this->tr()->translate('shop.flash.csrf'), 'error');
        }
        $files = $_FILES['file'] ?? null;
        if (!is_array($files) || ($files['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
            return $this->redirect('/admin/shop/products/import', $this->tr()->translate('shop.flash.csv_no_file'), 'error');
        }
        $delim = (string) $request->input('delimiter', ',');
        if (!in_array($delim, [',', ';', "\t"], true)) $delim = ',';

        $stats = ['inserted' => 0, 'updated' => 0, 'skipped' => 0, 'errors' => []];
        $fh = @fopen($files['tmp_name'], 'r');
        if ($fh === false) {
            return $this->redirect('/admin/shop/products/import', $this->tr()->translate('shop.flash.csv_open_error'), 'error');
        }
        // PHP 8.4 требует явный $escape — передаём пустую строку, чтобы отключить
        // сомнительный backslash-escape (стандартное поведение CSV).
        $header = fgetcsv($fh, 0, $delim, '"', '');
        if ($header === false) {
            fclose($fh);
            return $this->redirect('/admin/shop/products/import', $this->tr()->translate('shop.flash.csv_empty'), 'error');
        }
        // Нормализуем имена колонок (lowercase, strip BOM на первой).
        $header[0] = preg_replace('/^\xEF\xBB\xBF/', '', (string) $header[0]);
        $header = array_map(fn ($h) => mb_strtolower(trim((string) $h), 'UTF-8'), $header);

        // Кэш справочников — по slug.
        $manufacturers = [];
        foreach ($this->conn->builder('shop_manufacturers')->get() as $m) {
            $manufacturers[(string) $m['slug']] = (int) $m['id'];
        }
        $categories = [];
        foreach ($this->conn->builder('celena_categories')
            ->where('entity_type', '=', 'shop_product')->get() as $c) {
            $categories[(string) $c['slug']] = (int) $c['id'];
        }

        $now = date('Y-m-d H:i:s');
        $line = 1;
        while (($row = fgetcsv($fh, 0, $delim, '"', '')) !== false) {
            $line++;
            if (count($row) === 1 && trim((string) $row[0]) === '') continue;
            $assoc = [];
            foreach ($header as $i => $col) {
                $assoc[$col] = $row[$i] ?? '';
            }
            try {
                $title = trim((string) ($assoc['title'] ?? ''));
                if ($title === '') {
                    $stats['skipped']++;
                    continue;
                }
                $slug = trim((string) ($assoc['slug'] ?? ''));
                if ($slug === '') $slug = Filter::slug($title);

                $data = [
                    'title' => $title,
                    'slug'  => $slug,
                    'sku'   => trim((string) ($assoc['sku'] ?? '')),
                    'barcode' => trim((string) ($assoc['barcode'] ?? '')),
                    'price' => (float) ($assoc['price'] ?? 0),
                    'old_price' => $assoc['old_price'] ?? '' === '' ? null : (float) $assoc['old_price'],
                    'stock' => (int) ($assoc['stock'] ?? 0),
                    'short' => trim((string) ($assoc['short'] ?? '')),
                    'body'  => trim((string) ($assoc['body'] ?? '')),
                    'cover' => trim((string) ($assoc['cover'] ?? '')),
                    'seo_title' => trim((string) ($assoc['seo_title'] ?? '')),
                    'seo_description' => trim((string) ($assoc['seo_description'] ?? '')),
                    'updated_at' => $now,
                ];
                $status = trim((string) ($assoc['status'] ?? 'active'));
                if (!in_array($status, ['active', 'draft', 'archived'], true)) $status = 'active';
                $data['status'] = $status;
                $data['is_active'] = $status === 'active';
                $data['is_featured'] = in_array(strtolower((string) ($assoc['is_featured'] ?? '')), ['1', 'yes', 'true', 'да'], true);

                $manSlug = trim((string) ($assoc['manufacturer'] ?? ''));
                $data['manufacturer_id'] = $manSlug !== '' ? ($manufacturers[$manSlug] ?? null) : null;
                $catSlug = trim((string) ($assoc['category'] ?? ''));
                $data['category_id'] = $catSlug !== '' ? ($categories[$catSlug] ?? null) : null;

                $existingById = null;
                if (!empty($assoc['id']) && ctype_digit((string) $assoc['id'])) {
                    $existingById = $this->conn->builder('shop_products')
                        ->where('id', '=', (int) $assoc['id'])->first();
                }
                $existing = $existingById ?: $this->conn->builder('shop_products')
                    ->where('slug', '=', $slug)->first();

                if ($existing !== null) {
                    $this->conn->builder('shop_products')
                        ->where('id', '=', (int) $existing['id'])->update($data);
                    $stats['updated']++;
                } else {
                    $data['created_at'] = $now;
                    $this->conn->builder('shop_products')->insert($data);
                    $stats['inserted']++;
                }
            } catch (\Throwable $e) {
                $stats['errors'][] = "Строка $line: " . $e->getMessage();
                $stats['skipped']++;
                if (count($stats['errors']) > 20) {
                    $stats['errors'][] = '...';
                    break;
                }
            }
        }
        fclose($fh);

        return $this->view('plugins/shop/admin/csv_import', [
            'stats' => $stats,
        ], 'shop-csv', $this->tr()->translate('shop.csv.import_result_title'));
    }

    /**
     * Описание всех колонок, доступных для экспорта.
     * Каждая колонка → читаемый label + extractor: получает строку товара
     * и возвращает значение нужного типа (int/float/string).
     *
     * @return array<string, array{label:string, group:string, default:bool, extract:callable}>
     */
    private function exportableColumns(array $manMap, array $catMap): array
    {
        $bool = static fn ($v) => !empty($v) ? '1' : '0';
        $str  = static fn (?string $k) => static fn ($r) => (string) ($r[$k] ?? '');
        $strNullable = static fn (string $k) => static fn ($r) => $r[$k] !== null && $r[$k] !== '' ? (string) $r[$k] : '';

        return [
            'id'              => ['label' => 'ID',                     'group' => 'Основное', 'default' => true,  'extract' => static fn ($r) => (int) $r['id']],
            'slug'            => ['label' => 'URL-slug',               'group' => 'Основное', 'default' => true,  'extract' => $str('slug')],
            'title'           => ['label' => 'Название',               'group' => 'Основное', 'default' => true,  'extract' => $str('title')],
            'sku'             => ['label' => 'Артикул (SKU)',          'group' => 'Основное', 'default' => true,  'extract' => $str('sku')],
            'barcode'         => ['label' => 'Штрихкод',               'group' => 'Основное', 'default' => false, 'extract' => $str('barcode')],
            'price'           => ['label' => 'Цена',                   'group' => 'Цены и склад', 'default' => true,  'extract' => static fn ($r) => (float) $r['price']],
            'old_price'       => ['label' => 'Старая цена',            'group' => 'Цены и склад', 'default' => true,  'extract' => static fn ($r) => $r['old_price'] !== null ? (float) $r['old_price'] : ''],
            'stock'           => ['label' => 'Остаток',                'group' => 'Цены и склад', 'default' => true,  'extract' => static fn ($r) => (int) $r['stock']],
            'status'          => ['label' => 'Статус',                 'group' => 'Публикация', 'default' => true,  'extract' => static fn ($r) => (string) ($r['status'] ?? 'active')],
            'is_featured'     => ['label' => 'Избранный',              'group' => 'Публикация', 'default' => false, 'extract' => static fn ($r) => $bool($r['is_featured'] ?? false)],
            'manufacturer'    => ['label' => 'Производитель (slug)',   'group' => 'Связи',    'default' => true,  'extract' => static fn ($r) => isset($r['manufacturer_id']) ? ($manMap[(int) $r['manufacturer_id']] ?? '') : ''],
            'category'        => ['label' => 'Категория (slug)',       'group' => 'Связи',    'default' => true,  'extract' => static fn ($r) => isset($r['category_id']) ? ($catMap[(int) $r['category_id']] ?? '') : ''],
            'short'           => ['label' => 'Краткое описание',       'group' => 'Тексты',   'default' => false, 'extract' => $str('short')],
            'body'            => ['label' => 'Полное описание',        'group' => 'Тексты',   'default' => false, 'extract' => $str('body')],
            'seo_title'       => ['label' => 'SEO Title',              'group' => 'SEO',      'default' => false, 'extract' => $str('seo_title')],
            'seo_description' => ['label' => 'SEO Description',        'group' => 'SEO',      'default' => false, 'extract' => $str('seo_description')],
            'cover'           => ['label' => 'Главное фото (URL)',     'group' => 'Медиа',    'default' => false, 'extract' => $str('cover')],
            'rating_avg'      => ['label' => 'Средний рейтинг',        'group' => 'Метрики',  'default' => false, 'extract' => static fn ($r) => (float) ($r['rating_avg'] ?? 0)],
            'rating_count'    => ['label' => 'Кол-во отзывов',         'group' => 'Метрики',  'default' => false, 'extract' => static fn ($r) => (int) ($r['rating_count'] ?? 0)],
            'views'           => ['label' => 'Просмотров',             'group' => 'Метрики',  'default' => false, 'extract' => static fn ($r) => (int) ($r['views'] ?? 0)],
            'created_at'      => ['label' => 'Дата создания',          'group' => 'Даты',     'default' => false, 'extract' => $strNullable('created_at')],
            'updated_at'      => ['label' => 'Дата обновления',        'group' => 'Даты',     'default' => false, 'extract' => $strNullable('updated_at')],
        ];
    }

    /** Форма выбора формата и колонок. */
    public function exportForm(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        $columns = $this->exportableColumns([], []);
        $grouped = [];
        foreach ($columns as $key => $meta) {
            $grouped[$meta['group']][] = ['key' => $key, 'label' => $meta['label'], 'default' => $meta['default']];
        }
        $groupsList = [];
        foreach ($grouped as $name => $cols) {
            $groupsList[] = ['name' => $name, 'columns' => $cols];
        }
        $totals = [
            'all'      => (int) $this->conn->builder('shop_products')->count(),
            'active'   => (int) $this->conn->builder('shop_products')->where('status', '=', 'active')->count(),
            'draft'    => (int) $this->conn->builder('shop_products')->where('status', '=', 'draft')->count(),
            'archived' => (int) $this->conn->builder('shop_products')->where('status', '=', 'archived')->count(),
        ];
        return $this->view('plugins/shop/admin/products_export', [
            'groups' => $groupsList,
            'totals' => $totals,
        ], 'shop-csv', 'Экспорт товаров');
    }

    /** Сам экспорт — строим файл в выбранном формате с выбранными колонками. */
    public function exportRun(Request $request): Response
    {
        if (!$this->auth->check()) return Response::redirect('/admin/login');
        if (!Csrf::validate((string) $request->input('_csrf'))) {
            return $this->redirect('/admin/shop/products/export', $this->tr()->translate('shop.flash.csrf'), 'error');
        }

        $format = (string) $request->input('format', 'csv');
        if (!in_array($format, ['csv', 'xlsx'], true)) $format = 'csv';

        $scope = (string) $request->input('scope', 'all');
        if (!in_array($scope, ['all', 'active', 'draft', 'archived'], true)) $scope = 'all';

        $picked = (array) $request->input('cols', []);
        $picked = array_values(array_filter(array_map('strval', $picked), fn ($s) => $s !== ''));

        // Справочники для разрешения id → slug.
        $manMap = [];
        foreach ($this->conn->builder('shop_manufacturers')->get() as $m) {
            $manMap[(int) $m['id']] = (string) $m['slug'];
        }
        $catMap = [];
        foreach ($this->conn->builder('celena_categories')
            ->where('entity_type', '=', 'shop_product')->get() as $c) {
            $catMap[(int) $c['id']] = (string) $c['slug'];
        }

        $columns = $this->exportableColumns($manMap, $catMap);
        // Если ничего не выбрали — берём дефолтный набор, чтобы файл не был пустым.
        $selected = [];
        foreach ($columns as $key => $meta) {
            if ($picked === [] ? $meta['default'] : in_array($key, $picked, true)) {
                $selected[$key] = $meta;
            }
        }
        if ($selected === []) {
            return $this->redirect('/admin/shop/products/export', $this->tr()->translate('shop.flash.csv_no_columns'), 'error');
        }

        // Выборка с учётом scope. orderBy id для стабильного порядка экспорта.
        $q = $this->conn->builder('shop_products');
        if ($scope !== 'all') $q->where('status', '=', $scope);
        $rows = $q->orderBy('id', 'asc')->get();

        $headers = array_map(fn ($m) => $m['label'], $selected);
        $dateStamp = date('Y-m-d');

        if ($format === 'xlsx') {
            $w = new \Celena\Plugin\Shop\Support\XlsxWriter('Products');
            $w->writeRow(array_values($headers), header: true);
            foreach ($rows as $r) {
                $line = [];
                foreach ($selected as $meta) {
                    $line[] = ($meta['extract'])($r);
                }
                $w->writeRow($line);
            }
            $bytes = $w->finish();
            return new Response($bytes, 200, [
                'Content-Type'        => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                'Content-Disposition' => 'attachment; filename="products-' . $dateStamp . '.xlsx"',
                'Content-Length'      => (string) strlen($bytes),
            ]);
        }

        // CSV ─ с BOM для корректного открытия в Excel.
        $fh = fopen('php://temp', 'r+');
        fwrite($fh, "\xEF\xBB\xBF");
        fputcsv($fh, array_values($headers), ',', '"', '');
        foreach ($rows as $r) {
            $line = [];
            foreach ($selected as $meta) {
                $line[] = ($meta['extract'])($r);
            }
            fputcsv($fh, $line, ',', '"', '');
        }
        rewind($fh);
        $csv = stream_get_contents($fh);
        fclose($fh);

        return new Response((string) $csv, 200, [
            'Content-Type'        => 'text/csv; charset=UTF-8',
            'Content-Disposition' => 'attachment; filename="products-' . $dateStamp . '.csv"',
        ]);
    }
}
