<?php

namespace Kylin\Excel\Import;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use Kylin\Excel\Concerns\ToArray;
use Kylin\Excel\Concerns\ToCollection;
use Kylin\Excel\Concerns\ToModel;
use Kylin\Excel\Concerns\WithChunkReading;
use Kylin\Excel\Concerns\WithEvents;
use Kylin\Excel\Concerns\WithHeadingRow;
use Kylin\Excel\Concerns\WithLimit;
use Kylin\Excel\Concerns\WithMapping;
use Kylin\Excel\Concerns\WithValidation;
use Kylin\Excel\Events\AfterImport;
use Kylin\Excel\Events\AfterSheet;
use Kylin\Excel\Events\BeforeImport;
use Kylin\Excel\Events\BeforeSheet;
use Kylin\Excel\Events\EventDispatcher;
use Kylin\Excel\Events\ImportFailed;
use Kylin\Excel\Exceptions\ExcelException;
use Vtiful\Kernel\Excel as XlsWriterExcel;

/**
 * Excel导入处理类
 * 
 * 负责Excel文件的读取和数据导入处理
 * 支持分块读取、标题行映射、数据转换、数据验证、事件触发等功能
 */
class Import
{
    /**
     * 导入对象
     * 
     * 实现了相应导入接口的对象，用于定义导入规则
     * 
     * @var object
     */
    protected object $importable;

    /**
     * Excel文件路径
     * 
     * @var string
     */
    protected string $filePath;

    /**
     * 工作表索引
     * 
     * 默认读取第一个工作表（索引为0）
     * 
     * @var int
     */
    protected int $sheetIndex = 0;

    /**
     * 分块大小
     * 
     * 用于大数据量导入时分批处理，默认每批1000条
     * 
     * @var int
     */
    protected int $chunkSize = 1000;

    /**
     * 起始行号
     * 
     * 数据开始的行号，默认从第1行开始
     * 如果有标题行，则从第2行开始
     * 
     * @var int
     */
    protected int $startRow = 1;

    /**
     * 是否有标题行
     * 
     * 标识Excel文件是否包含标题行
     * 
     * @var bool
     */
    protected bool $hasHeadingRow = false;

    /**
     * 标题数组
     * 
     * 存储Excel文件的标题行数据
     * 
     * @var array
     */
    protected array $headings = [];

    /**
     * 读取行数限制
     * 
     * 最大读取行数，0表示无限制
     * 
     * @var int
     */
    protected int $limit = 0;

    /**
     * 是否启用验证
     * 
     * @var bool
     */
    protected bool $hasValidation = false;

    /**
     * 事件调度器
     * 
     * @var EventDispatcher
     */
    protected EventDispatcher $eventDispatcher;

    /**
     * 构造函数
     * 
     * 初始化导入处理器并解析导入对象的配置
     * 
     * @param object $importable 导入对象
     */
    public function __construct(object $importable)
    {
        $this->importable = $importable;
        $this->eventDispatcher = new EventDispatcher();
        $this->resolveImportable();
    }

    /**
     * 解析导入对象配置
     * 
     * 根据导入对象实现的接口，提取相应的配置信息
     * 如分块大小、是否有标题行、读取限制等
     * 
     * @return void
     */
    protected function resolveImportable(): void
    {
        if ($this->importable instanceof WithChunkReading) {
            $this->chunkSize = $this->importable->chunkSize();
        }

        if ($this->importable instanceof WithHeadingRow) {
            $this->hasHeadingRow = true;
            $this->startRow = 2;
        }

        if ($this->importable instanceof WithLimit) {
            $this->limit = $this->importable->limit();
        }

        if ($this->importable instanceof WithValidation) {
            $this->hasValidation = true;
        }

        if ($this->importable instanceof WithEvents) {
            $this->eventDispatcher->registerFromWithEvents($this->importable);
        }
    }

    /**
     * 执行导入
     * 
     * 读取Excel文件并处理导入逻辑
     * 
     * @param string $filePath Excel文件路径
     * @return void
     * @throws ExcelException 如果文件不存在或导入失败
     */
    public function import(string $filePath): void
    {
        $this->filePath = $filePath;

        if (!file_exists($this->filePath)) {
            throw ExcelException::fileNotFound($this->filePath);
        }

        $this->fireEvent(BeforeImport::class);

        try {
            $this->readFile();
            $this->fireEvent(AfterImport::class);
        } catch (\Throwable $e) {
            $this->fireEvent(ImportFailed::class, ['exception' => $e]);
            throw $e;
        }
    }

    /**
     * 读取Excel为集合
     * 
     * 读取Excel文件内容并转换为Laravel Collection对象
     * 
     * @param string $filePath Excel文件路径
     * @return Collection 包含Excel数据的集合
     * @throws ExcelException 如果文件不存在
     */
    public function toCollection(string $filePath): Collection
    {
        $this->filePath = $filePath;

        if (!file_exists($this->filePath)) {
            throw ExcelException::fileNotFound($this->filePath);
        }

        return $this->readToCollection();
    }

    /**
     * 读取Excel为数组
     * 
     * 读取Excel文件内容并转换为PHP数组
     * 
     * @param string $filePath Excel文件路径
     * @return array 包含Excel数据的数组
     */
    public function toArray(string $filePath): array
    {
        return $this->toCollection($filePath)->toArray();
    }

    /**
     * 读取文件
     * 
     * 读取Excel文件内容并处理数据
     * 
     * @return void
     */
    protected function readFile(): void
    {
        $data = $this->readExcelData();

        if ($this->hasHeadingRow && !empty($data)) {
            $this->headings = array_shift($data);
        }

        if ($this->limit > 0 && count($data) > $this->limit) {
            $data = array_slice($data, 0, $this->limit);
        }

        $this->fireEvent(BeforeSheet::class);
        $this->processData($data);
        $this->fireEvent(AfterSheet::class);
    }

    /**
     * 读取Excel为集合（内部方法）
     * 
     * 读取Excel数据并进行数据转换处理
     * 
     * @return Collection 处理后的数据集合
     */
    protected function readToCollection(): Collection
    {
        $data = $this->readExcelData();

        if ($this->hasHeadingRow && !empty($data)) {
            $this->headings = array_shift($data);
        }

        if ($this->limit > 0 && count($data) > $this->limit) {
            $data = array_slice($data, 0, $this->limit);
        }

        if ($this->importable instanceof WithMapping) {
            $data = array_map(
                fn ($row) => $this->importable->map($row),
                $data
            );
        }

        if (!empty($this->headings)) {
            $data = array_map(function ($row) {
                return array_combine($this->headings, $row) ?: $row;
            }, $data);
        }

        return collect($data);
    }

    /**
     * 读取Excel原始数据
     * 
     * 使用xlswriter扩展读取Excel文件的原始数据
     * 
     * @return array Excel数据数组
     * @throws ExcelException 如果工作表不存在
     */
    protected function readExcelData(): array
    {
        $config = ['path' => dirname($this->filePath)];
        $excel = new XlsWriterExcel($config);
        $excel->openFile(basename($this->filePath));

        $sheetList = $excel->sheetList();
        $sheetName = $sheetList[$this->sheetIndex] ?? $sheetList[0] ?? null;

        if ($sheetName === null) {
            throw ExcelException::sheetNotFound($this->sheetIndex);
        }

        $excel->openSheet($sheetName);

        return $excel->getSheetData() ?: [];
    }

    /**
     * 处理数据
     * 
     * 根据导入对象实现的接口，将数据分块处理
     * 支持ToCollection、ToArray、ToModel等接口
     * 
     * @param array $data Excel数据数组
     * @return void
     * @throws ExcelException 如果验证失败
     */
    protected function processData(array $data): void
    {
        $chunks = array_chunk($data, $this->chunkSize);

        foreach ($chunks as $chunk) {
            if ($this->importable instanceof WithMapping) {
                $chunk = array_map(
                    fn ($row) => $this->importable->map($row),
                    $chunk
                );
            }

            if ($this->hasValidation) {
                $this->validateData($chunk);
            }

            if ($this->importable instanceof ToCollection) {
                $this->importable->collection(collect($chunk));
            } elseif ($this->importable instanceof ToArray) {
                $this->importable->array($chunk);
            } elseif ($this->importable instanceof ToModel) {
                foreach ($chunk as $row) {
                    $model = $this->importable->model($row);
                    if ($model) {
                        $model->save();
                    }
                }
            }
        }
    }

    /**
     * 验证数据
     * 
     * 使用Laravel验证器验证数据
     * 
     * @param array $data 待验证的数据
     * @throws ExcelException 如果验证失败
     */
    protected function validateData(array $data): void
    {
        $rules = $this->importable->rules();
        $customAttributes = [];

        if (method_exists($this->importable, 'customAttributes')) {
            $customAttributes = $this->importable->customAttributes();
        }

        foreach ($data as $index => $row) {
            $rowData = $this->prepareRowForValidation($row);
            $validator = Validator::make($rowData, $rules, [], $customAttributes);

            if ($validator->fails()) {
                $errors = $validator->errors()->all();
                throw ExcelException::importFailed(
                    sprintf('第%d行数据验证失败: %s', $index + $this->startRow, implode(', ', $errors))
                );
            }
        }
    }

    /**
     * 准备行数据用于验证
     * 
     * 将行数据转换为验证器期望的格式
     * 
     * @param array $row 行数据
     * @return array 准备好的数据
     */
    protected function prepareRowForValidation(array $row): array
    {
        $prepared = [];
        foreach ($row as $key => $value) {
            $prepared[(string) $key] = $value;
        }
        return $prepared;
    }

    /**
     * 触发事件
     * 
     * @param string $eventClass 事件类名
     * @param array $payload 事件附加数据
     * @return void
     */
    protected function fireEvent(string $eventClass, array $payload = []): void
    {
        if (!$this->eventDispatcher->hasListeners($eventClass)) {
            return;
        }

        $event = new $eventClass($this->importable, ...$payload);
        $this->eventDispatcher->dispatch($event);
    }

    /**
     * 设置工作表索引
     * 
     * 指定要读取的工作表索引
     * 
     * @param int $index 工作表索引（从0开始）
     * @return self 支持链式调用
     */
    public function setSheetIndex(int $index): self
    {
        $this->sheetIndex = $index;
        return $this;
    }

    /**
     * 设置分块大小
     * 
     * 自定义数据分块处理的大小
     * 
     * @param int $size 每块的记录数
     * @return self 支持链式调用
     */
    public function setChunkSize(int $size): self
    {
        $this->chunkSize = $size;
        return $this;
    }

    /**
     * 设置读取行数限制
     * 
     * @param int $limit 最大读取行数
     * @return self 支持链式调用
     */
    public function setLimit(int $limit): self
    {
        $this->limit = $limit;
        return $this;
    }

    /**
     * 获取标题数组
     * 
     * 返回Excel文件的标题行数据
     * 
     * @return array 标题数组
     */
    public function getHeadings(): array
    {
        return $this->headings;
    }

    /**
     * 获取所有工作表名称
     * 
     * 返回Excel文件中所有工作表的名称列表
     * 
     * @return array 工作表名称数组
     */
    public function getSheetNames(): array
    {
        $config = ['path' => dirname($this->filePath)];
        $excel = new XlsWriterExcel($config);
        $excel->openFile(basename($this->filePath));

        return $excel->sheetList();
    }
}
