<?php

namespace Kylin\Excel\Services;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Kylin\Excel\Concerns\FromArray;
use Kylin\Excel\Concerns\FromCollection;
use Kylin\Excel\Concerns\FromQuery;
use Kylin\Excel\Concerns\ShouldQueue;
use Kylin\Excel\Concerns\ToArray;
use Kylin\Excel\Concerns\ToCollection;
use Kylin\Excel\Concerns\ToModel;
use Kylin\Excel\Concerns\WithMultipleSheets;
use Kylin\Excel\Exceptions\ExcelException;
use Kylin\Excel\Export\Export;
use Kylin\Excel\Import\Import;
use Vtiful\Kernel\Excel as XlsWriterExcel;

/**
 * Excel服务类
 * 
 * 核心服务类，提供Excel导入导出的具体实现
 * 基于xlswriter扩展实现高性能的Excel操作
 */
class ExcelService
{
    /**
     * 配置数组
     * 
     * @var array
     */
    protected array $config;

    /**
     * 构造函数
     * 
     * 初始化服务并检查必要扩展是否加载
     * 
     * @param array $config 配置数组
     */
    public function __construct(array $config = [])
    {
        $this->config = $config;
        $this->ensureExtensionLoaded();
    }

    /**
     * 确保xlswriter扩展已加载
     * 
     * 检查PHP环境是否安装并启用了xlswriter扩展
     * 
     * @return void
     * @throws ExcelException 如果扩展未加载
     */
    protected function ensureExtensionLoaded(): void
    {
        if (!extension_loaded('xlswriter')) {
            throw ExcelException::extensionNotLoaded();
        }
    }

    /**
     * 解析文件路径
     * 
     * 根据磁盘名称获取实际的文件路径
     * 
     * @param string $filePath 文件路径
     * @param string|null $disk 存储磁盘名称
     * @return string 实际文件路径
     */
    protected function resolvePath(string $filePath, ?string $disk = null): string
    {
        if ($disk === null) {
            return $filePath;
        }

        return Storage::disk($disk)->path($filePath);
    }

    /**
     * 下载Excel文件
     * 
     * 将导出数据转换为Excel文件并通过浏览器下载
     * 支持单工作表和多工作表导出
     * 
     * @param object $export 导出对象
     * @param string $fileName 下载文件名
     * @param string|null $writerType 写入器类型（暂未使用）
     * @return void
     */
    public function download(object $export, string $fileName, ?string $writerType = null): void
    {
        if ($export instanceof WithMultipleSheets) {
            $this->downloadMultipleSheets($export, $fileName);
            return;
        }

        $exporter = new Export($export);
        $exporter->download($fileName);
    }

    /**
     * 下载多工作表Excel文件
     * 
     * 处理包含多个工作表的导出，生成临时文件后下载
     * 
     * @param object $export 导出对象（实现WithMultipleSheets接口）
     * @param string $fileName 下载文件名
     * @return void
     */
    protected function downloadMultipleSheets(object $export, string $fileName): void
    {
        $sheets = $export->sheets();
        $tempPath = sys_get_temp_dir() . '/' . uniqid('excel_', true) . '.xlsx';

        $config = ['path' => dirname($tempPath)];
        $excel = new XlsWriterExcel($config);
        $fileHandle = $excel->fileName(basename($tempPath));

        $isFirstSheet = true;
        foreach ($sheets as $sheetExport) {
            $sheetExporter = new Export($sheetExport);

            if ($isFirstSheet) {
                $fileHandle->sheet($sheetExporter->getSheetName());
                $isFirstSheet = false;
            } else {
                $fileHandle->addSheet($sheetExporter->getSheetName());
            }

            $this->writeSheetData($fileHandle, $sheetExport);
        }

        $fileHandle->output();

        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        header('Content-Disposition: attachment;filename="' . $fileName . '"');
        header('Cache-Control: max-age=0');

        readfile($tempPath);
        unlink($tempPath);
        exit;
    }

    /**
     * 写入工作表数据
     * 
     * 将导出数据写入到指定的工作表中
     * 支持多种数据源：Collection、Array、Query
     * 
     * @param mixed $fileHandle 文件句柄
     * @param object $sheetExport 工作表导出对象
     * @return void
     */
    protected function writeSheetData($fileHandle, object $sheetExport): void
    {
        $data = [];

        if ($sheetExport instanceof FromCollection) {
            $data = collect($sheetExport->collection())->toArray();
        } elseif ($sheetExport instanceof FromArray) {
            $data = $sheetExport->array();
        } elseif ($sheetExport instanceof FromQuery) {
            $data = $sheetExport->query()->get()->toArray();
        }

        if ($sheetExport instanceof \Kylin\Excel\Concerns\WithHeadings) {
            $fileHandle->header($sheetExport->headings());
        }

        if ($sheetExport instanceof \Kylin\Excel\Concerns\WithMapping) {
            $data = array_map(fn ($row) => $sheetExport->map($row), $data);
        }

        if (!empty($data)) {
            $fileHandle->data($data);
        }
    }

    /**
     * 存储Excel文件
     * 
     * 将导出数据保存到指定路径
     * 支持单工作表和多工作表导出
     * 
     * @param object $export 导出对象
     * @param string $filePath 存储路径
     * @param string|null $disk 存储磁盘名称
     * @param string|null $writerType 写入器类型（暂未使用）
     * @return bool 返回是否存储成功
     */
    public function store(object $export, string $filePath, ?string $disk = null, ?string $writerType = null): bool
    {
        $realPath = $this->resolvePath($filePath, $disk);

        if ($export instanceof WithMultipleSheets) {
            return $this->storeMultipleSheets($export, $realPath);
        }

        $exporter = new Export($export);
        return $exporter->store($realPath);
    }

    /**
     * 存储多工作表Excel文件
     * 
     * 处理包含多个工作表的导出并保存到指定路径
     * 
     * @param object $export 导出对象（实现WithMultipleSheets接口）
     * @param string $filePath 存储路径
     * @return bool 返回是否存储成功
     */
    protected function storeMultipleSheets(object $export, string $filePath): bool
    {
        $sheets = $export->sheets();

        $config = ['path' => dirname($filePath)];
        $excel = new XlsWriterExcel($config);
        $fileHandle = $excel->fileName(basename($filePath));

        $isFirstSheet = true;
        foreach ($sheets as $sheetExport) {
            $sheetExporter = new Export($sheetExport);

            if ($isFirstSheet) {
                $fileHandle->sheet($sheetExporter->getSheetName());
                $isFirstSheet = false;
            } else {
                $fileHandle->addSheet($sheetExporter->getSheetName());
            }

            $this->writeSheetData($fileHandle, $sheetExport);
        }

        $fileHandle->output();
        return true;
    }

    /**
     * 获取Excel原始内容
     * 
     * 将导出数据转换为Excel文件的原始二进制字符串
     * 适用于需要自定义响应的场景
     * 
     * @param object $export 导出对象
     * @param string|null $writerType 写入器类型（暂未使用）
     * @return string Excel文件的原始二进制内容
     */
    public function raw(object $export, ?string $writerType = null): string
    {
        $exporter = new Export($export);
        return $exporter->raw();
    }

    /**
     * 导入Excel文件
     * 
     * 读取Excel文件内容并处理导入逻辑
     * 支持队列异步导入
     * 
     * @param object $import 导入对象
     * @param string $filePath 文件路径
     * @param string|null $disk 存储磁盘名称
     * @param string|null $readerType 读取器类型（暂未使用）
     * @return void
     */
    public function import(object $import, string $filePath, ?string $disk = null, ?string $readerType = null): void
    {
        if ($import instanceof ShouldQueue) {
            $this->queueImport($import, $filePath, $disk);
            return;
        }

        $realPath = $this->resolvePath($filePath, $disk);
        $importer = new Import($import);
        $importer->import($realPath);
    }

    /**
     * 队列导入Excel文件
     * 
     * 将导入任务放入队列异步执行
     * 适用于大数据量导入场景
     * 
     * @param object $import 导入对象
     * @param string $filePath 文件路径
     * @param string|null $disk 存储磁盘名称
     * @return void
     */
    protected function queueImport(object $import, string $filePath, ?string $disk): void
    {
        dispatch(function () use ($import, $filePath, $disk): void {
            $realPath = $this->resolvePath($filePath, $disk);
            $importer = new Import($import);
            $importer->import($realPath);
        });
    }

    /**
     * 将Excel数据读取为集合
     * 
     * 读取Excel文件内容并转换为Laravel Collection对象
     * 
     * @param object $import 导入对象
     * @param string $filePath 文件路径
     * @param string|null $disk 存储磁盘名称
     * @param string|null $readerType 读取器类型（暂未使用）
     * @return Collection 包含Excel数据的集合
     */
    public function toCollection(object $import, string $filePath, ?string $disk = null, ?string $readerType = null): Collection
    {
        $realPath = $this->resolvePath($filePath, $disk);
        $importer = new Import($import);
        return $importer->toCollection($realPath);
    }

    /**
     * 将Excel数据读取为数组
     * 
     * 读取Excel文件内容并转换为PHP数组
     * 
     * @param object $import 导入对象
     * @param string $filePath 文件路径
     * @param string|null $disk 存储磁盘名称
     * @param string|null $readerType 读取器类型（暂未使用）
     * @return array 包含Excel数据的数组
     */
    public function toArray(object $import, string $filePath, ?string $disk = null, ?string $readerType = null): array
    {
        $realPath = $this->resolvePath($filePath, $disk);
        $importer = new Import($import);
        return $importer->toArray($realPath);
    }

    /**
     * 队列导出Excel
     * 
     * 将导出任务放入队列异步执行
     * 适用于大数据量导出场景
     * 
     * @param object $export 导出对象
     * @param string $filePath 存储路径
     * @param string|null $disk 存储磁盘名称
     * @return void
     */
    public function queue(object $export, string $filePath, ?string $disk = null): void
    {
        dispatch(function () use ($export, $filePath, $disk): void {
            $this->store($export, $filePath, $disk);
        });
    }

    /**
     * 链式执行多个任务
     * 
     * 按顺序执行多个队列任务
     * 
     * @param array $chain 任务数组
     * @return void
     */
    public function chain(array $chain): void
    {
        foreach ($chain as $job) {
            if ($job instanceof \Illuminate\Contracts\Queue\ShouldQueue) {
                dispatch($job);
            }
        }
    }

    /**
     * 创建Excel写入器
     * 
     * 创建一个新的xlswriter写入器实例
     * 
     * @param string $filePath 文件路径
     * @return XlsWriterExcel xlswriter实例
     */
    public function createWriter(string $filePath): XlsWriterExcel
    {
        $config = ['path' => dirname($filePath)];
        return new XlsWriterExcel($config);
    }

    /**
     * 创建Excel读取器
     * 
     * 创建一个新的xlswriter读取器实例
     * 
     * @param string $filePath 文件路径
     * @return XlsWriterExcel xlswriter实例
     */
    public function createReader(string $filePath): XlsWriterExcel
    {
        $config = ['path' => dirname($filePath)];
        return new XlsWriterExcel($config);
    }
}
