<?php

namespace Kylin\Storage\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Manager;
use Illuminate\Support\Str;
use Kylin\Storage\Contracts\StorageAdapterInterface;
use Kylin\Storage\Adapters\LocalAdapter;
use Kylin\Storage\Adapters\QiniuAdapter;
use Kylin\Storage\Adapters\TencentAdapter;
use Kylin\Storage\Adapters\AliyunAdapter;
use Kylin\Storage\Adapters\HuaweiAdapter;
use Kylin\Storage\Adapters\S3Adapter;
use Kylin\Storage\Adapters\KingsoftAdapter;
use Kylin\Storage\Adapters\BaiduAdapter;
use Kylin\Storage\Models\File;

/**
 * 存储服务
 *
 * 合规要求：
 *  - 依据《网络安全法》第二十一条，文件上传需验证大小和类型
 *  - 依据等保 2.0 (GB/T 22239) 8.1.4.6，文件操作需记录审计日志
 */
class StorageService
{
    protected string $defaultDriver;
    protected array $adapters = [];

    public function __construct()
    {
        $this->defaultDriver = config('kylin-storage.default', 'local');
    }

    public function adapter(?string $driver = null): StorageAdapterInterface
    {
        $driver = $driver ?? $this->defaultDriver;

        if (!isset($this->adapters[$driver])) {
            $this->adapters[$driver] = $this->createAdapter($driver);
        }

        return $this->adapters[$driver];
    }

    /**
     * 上传文件
     *
     * 依据《网络安全法》第二十一条，上传前验证文件大小和类型。
     * 依据等保 2.0 (GB/T 22239) 8.1.4.6，记录审计日志。
     * 依据《数据安全法》第二十五条，文件去重减少冗余存储。
     *
     * @param bool $deduplicate 是否启用去重（基于 hash），默认 true
     * @throws \InvalidArgumentException 文件大小或类型不符合要求
     */
    public function upload(UploadedFile $file, string $collection = 'default', ?string $driver = null, bool $deduplicate = true): File
    {
        $this->validateFile($file);

        $adapter = $this->adapter($driver);
        $hash = hash_file('sha256', $file->getRealPath());

        // 文件去重：相同 hash 的文件直接返回已有记录
        if ($deduplicate) {
            $existing = File::where('hash', $hash)
                ->where('driver', $driver ?? $this->defaultDriver)
                ->first();

            if ($existing) {
                $this->audit('file_deduplicated', 'Kylin\Storage\Models\File', $existing->id, [
                    'original_name' => $file->getClientOriginalName(),
                    'existing_path' => $existing->path,
                    'hash' => $hash,
                ]);

                return $existing;
            }
        }

        $remotePath = $this->generatePath($collection, $file);

        $adapter->upload($file->getRealPath(), $remotePath);

        $fileRecord = File::create([
            'driver' => $driver ?? $this->defaultDriver,
            'path' => $remotePath,
            'original_name' => $file->getClientOriginalName(),
            'mime_type' => $file->getMimeType(),
            'size' => $file->getSize(),
            'hash' => $hash,
            'collection' => $collection,
            'user_id' => auth()->id(),
        ]);

        $this->audit('file_uploaded', 'Kylin\Storage\Models\File', $fileRecord->id, [
            'original_name' => $file->getClientOriginalName(),
            'mime_type' => $file->getMimeType(),
            'size' => $file->getSize(),
            'collection' => $collection,
            'driver' => $driver ?? $this->defaultDriver,
            'path' => $remotePath,
        ]);

        return $fileRecord;
    }

    public function uploadFromString(string $content, string $remotePath, string $collection = 'default', ?string $driver = null): File
    {
        $adapter = $this->adapter($driver);
        $tmpPath = tempnam(sys_get_temp_dir(), 'kylin_');
        file_put_contents($tmpPath, $content);

        $adapter->upload($tmpPath, $remotePath);
        unlink($tmpPath);

        $fileRecord = File::create([
            'driver' => $driver ?? $this->defaultDriver,
            'path' => $remotePath,
            'original_name' => basename($remotePath),
            'mime_type' => mime_content_type($tmpPath) ?: 'application/octet-stream',
            'size' => strlen($content),
            'hash' => hash('sha256', $content),
            'collection' => $collection,
            'user_id' => auth()->id(),
        ]);

        $this->audit('file_uploaded', 'Kylin\Storage\Models\File', $fileRecord->id, [
            'original_name' => basename($remotePath),
            'size' => strlen($content),
            'collection' => $collection,
            'driver' => $driver ?? $this->defaultDriver,
            'path' => $remotePath,
        ]);

        return $fileRecord;
    }

    public function delete(File $file): bool
    {
        $this->adapter($file->driver)->delete($file->path);
        $result = $file->delete();

        $this->audit('file_deleted', 'Kylin\Storage\Models\File', $file->id, null, [
            'original_name' => $file->original_name,
            'path' => $file->path,
            'driver' => $file->driver,
        ]);

        return $result;
    }

    public function getUrl(File $file): string
    {
        return $this->adapter($file->driver)->getUrl($file->path);
    }

    public function getSignedUrl(File $file, int $expires = 3600): string
    {
        return $this->adapter($file->driver)->getSignedUrl($file->path, $expires);
    }

    public function exists(File $file): bool
    {
        return $this->adapter($file->driver)->exists($file->path);
    }

    public function copy(File $file, string $newPath, ?string $driver = null): File
    {
        $sourceDriver = $this->adapter($file->driver);
        $targetDriver = $this->adapter($driver);
        $localPath = $sourceDriver->download($file->path);

        $targetDriver->upload($localPath, $newPath);
        unlink($localPath);

        $newFile = File::create([
            'driver' => $driver ?? $this->defaultDriver,
            'path' => $newPath,
            'original_name' => $file->original_name,
            'mime_type' => $file->mime_type,
            'size' => $file->size,
            'hash' => $file->hash,
            'collection' => $file->collection,
            'user_id' => auth()->id(),
        ]);

        $this->audit('file_copied', 'Kylin\Storage\Models\File', $file->id, [
            'new_path' => $newPath,
            'new_driver' => $driver ?? $this->defaultDriver,
            'original_name' => $file->original_name,
        ]);

        return $newFile;
    }

    public function move(File $file, string $newPath, ?string $driver = null): File
    {
        $newFile = $this->copy($file, $newPath, $driver);
        $this->delete($file);
        return $newFile;
    }

    /**
     * 验证文件大小和类型
     *
     * 依据《网络安全法》第二十一条，验证上传文件的大小和类型。
     *
     * @throws \InvalidArgumentException
     */
    private function validateFile(UploadedFile $file): void
    {
        $maxFileSize = config('kylin-storage.max_file_size', 10240); // KB
        $allowedMimes = config('kylin-storage.allowed_mimes', []);

        // 验证文件大小
        $fileSizeKB = $file->getSize() / 1024;
        if ($fileSizeKB > $maxFileSize) {
            throw new \InvalidArgumentException(
                sprintf('文件大小 %.2f KB 超过限制 %d KB', $fileSizeKB, $maxFileSize)
            );
        }

        // 验证 MIME 类型
        if (! empty($allowedMimes)) {
            $mimeType = $file->getMimeType();
            if (! in_array($mimeType, $allowedMimes, true)) {
                throw new \InvalidArgumentException(
                    sprintf('不支持的文件类型: %s，允许的类型: %s', $mimeType, implode(', ', $allowedMimes))
                );
            }
        }
    }

    /**
     * 写入审计日志
     *
     * 依据等保 2.0 (GB/T 22239) 8.1.4.6。
     */
    private function audit(string $event, string $modelType, int|string $modelId, ?array $newValues = null, ?array $oldValues = null): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => $modelType,
                'model_id' => $modelId,
                'new_values' => $newValues,
                'old_values' => $oldValues,
                'user_id' => auth()->id(),
                'ip' => request()->ip(),
                'url' => request()->fullUrl(),
                'tag' => 'storage',
            ]);
        } catch (\Throwable $e) {
            logger()->warning('存储审计日志写入失败', [
                'event' => $event,
                'error' => $e->getMessage(),
            ]);
        }
    }

    private function createAdapter(string $driver): StorageAdapterInterface
    {
        $config = config("kylin-storage.drivers.{$driver}", []);

        return match ($driver) {
            'local' => new LocalAdapter($config),
            'qiniu' => new QiniuAdapter($config),
            'tencent' => new TencentAdapter($config),
            'aliyun' => new AliyunAdapter($config),
            'huawei' => new HuaweiAdapter($config),
            's3' => new S3Adapter($config),
            'kingsoft' => new KingsoftAdapter($config),
            'baidu' => new BaiduAdapter($config),
            default => throw new \InvalidArgumentException("Unsupported storage driver: {$driver}"),
        };
    }

    private function generatePath(string $collection, UploadedFile $file): string
    {
        $date = date('Y/m/d');
        $hash = substr(hash('sha256', uniqid((string) mt_rand(), true)), 0, 16);
        $extension = $file->getClientOriginalExtension() ?: $file->guessExtension();

        return "{$collection}/{$date}/{$hash}.{$extension}";
    }
}
