<?php

declare(strict_types=1);

namespace app\common\library;

use app\common\model\MallDailyPush;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Writer\XLSX\Options;
use OpenSpout\Writer\XLSX\Writer;
use support\Response;
use Throwable;

/**
 * 每日推送数据导出（OpenSpout 流式 XLSX）
 */
class MallDailyPushExport
{
    /** 0 表示不限制条数，导出全部匹配数据 */
    public const UNLIMITED_EXPORT = 0;

    public const CHUNK_SIZE = 2000;

    /**
     * @var array<string, string>
     */
    private array $fieldLabels = [
        'id'                        => 'ID',
        'user_id'                   => 'playX-ID',
        'date'                      => '业务日期',
        'username'                  => '用户名',
        'yesterday_win_loss_net'    => '昨日净输赢',
        'yesterday_total_deposit'   => '昨日总充值',
        'lifetime_total_deposit'    => '历史总充值',
        'lifetime_total_withdraw'   => '历史总提现',
        'create_time'               => '创建时间',
    ];

    /**
     * @var array<string, string>
     */
    private array $fieldLabelsEn = [
        'id'                        => 'ID',
        'user_id'                   => 'PlayX user ID',
        'date'                      => 'Business date',
        'username'                  => 'Username',
        'yesterday_win_loss_net'    => 'Yesterday net win/loss',
        'yesterday_total_deposit'   => 'Yesterday total deposit',
        'lifetime_total_deposit'    => 'Lifetime total deposit',
        'lifetime_total_withdraw'   => 'Lifetime total withdraw',
        'create_time'               => 'Created at',
    ];

    /**
     * @param array<int, string> $fields
     */
    public function export(
        MallDailyPush $model,
        array $where,
        array $alias,
        array $order,
        array $fields,
        int $limit,
        string $lang = 'zh-cn'
    ): Response {
        $fields = $this->normalizeFields($fields);
        if ($fields === []) {
            throw new \InvalidArgumentException(__('Parameter error'));
        }

        if ($limit < 0) {
            throw new \InvalidArgumentException(__('Parameter error'));
        }

        @set_time_limit(0);

        $exportDir = runtime_path('export');
        if (!is_dir($exportDir) && !mkdir($exportDir, 0755, true) && !is_dir($exportDir)) {
            throw new \RuntimeException('Failed to create export directory');
        }

        $filepath = $exportDir . DIRECTORY_SEPARATOR . 'daily_push_' . date('YmdHis') . '_' . bin2hex(random_bytes(4)) . '.xlsx';
        $labels = $this->resolveFieldLabels($lang);

        $options = new Options();
        $options->setTempFolder($exportDir);
        $writer = new Writer($options);

        try {
            $writer->openToFile($filepath);
            $writer->addRow(Row::fromValues($this->buildHeaderRow($fields, $labels)));

            $exported = 0;
            $page = 1;
            $unlimited = $limit === self::UNLIMITED_EXPORT;

            while ($unlimited || $exported < $limit) {
                $batchSize = $unlimited ? self::CHUNK_SIZE : min(self::CHUNK_SIZE, $limit - $exported);
                $rows = $model
                    ->field($fields)
                    ->alias($alias)
                    ->where($where)
                    ->order($order)
                    ->page($page, $batchSize)
                    ->select();

                if ($rows->isEmpty()) {
                    break;
                }

                foreach ($rows as $row) {
                    $writer->addRow(Row::fromValues($this->formatRow($row->toArray(), $fields)));
                    $exported++;
                    if (!$unlimited && $exported >= $limit) {
                        break;
                    }
                }

                if ($rows->count() < $batchSize) {
                    break;
                }
                $page++;
            }

            $writer->close();
        } catch (Throwable $e) {
            try {
                $writer->close();
            } catch (Throwable) {
                // ignore
            }
            if (is_file($filepath)) {
                @unlink($filepath);
            }
            throw $e;
        }

        register_shutdown_function(static function () use ($filepath): void {
            if (is_file($filepath)) {
                @unlink($filepath);
            }
        });

        $filename = 'daily_push_' . date('YmdHis') . '.xlsx';
        return (new Response())->file($filepath, $filename);
    }

    /**
     * @param array<int, string> $fields
     * @return array<int, string>
     */
    public function normalizeFields(array $fields): array
    {
        $allowed = array_keys($this->fieldLabels);
        $normalized = [];
        foreach ($fields as $field) {
            $field = trim(strval($field));
            if ($field !== '' && in_array($field, $allowed, true) && !in_array($field, $normalized, true)) {
                $normalized[] = $field;
            }
        }
        return $normalized;
    }

    /**
     * @return array<int, string>
     */
    public function getDefaultFields(): array
    {
        return array_keys($this->fieldLabels);
    }

    public function getMaxExportLimit(): int
    {
        return self::UNLIMITED_EXPORT;
    }

    /**
     * @param array<int, string> $fields
     * @param array<string, string> $labels
     * @return array<int, string>
     */
    private function buildHeaderRow(array $fields, array $labels): array
    {
        $header = [];
        foreach ($fields as $field) {
            $header[] = $labels[$field] ?? $field;
        }
        return $header;
    }

    /**
     * @param array<string, mixed> $row
     * @param array<int, string> $fields
     * @return array<int, string>
     */
    private function formatRow(array $row, array $fields): array
    {
        $line = [];
        foreach ($fields as $field) {
            $value = $row[$field] ?? '';
            if ($field === 'create_time' && $value !== '' && $value !== null) {
                $timestamp = is_numeric($value) ? intval($value) : strtotime(strval($value));
                $line[] = $timestamp > 0 ? date('Y-m-d H:i:s', $timestamp) : '';
                continue;
            }
            $line[] = $value === null ? '' : strval($value);
        }
        return $line;
    }

    /**
     * @return array<string, string>
     */
    private function resolveFieldLabels(string $lang): array
    {
        $lang = strtolower(str_replace('_', '-', $lang));
        return $lang === 'en' ? $this->fieldLabelsEn : $this->fieldLabels;
    }
}
