<?php

namespace app\service\file;

use app\entity\file\UploadResult;
use app\entity\SysFileRecord;
use app\entity\SysFileRule;
use app\entity\SysUserFile;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Event;
use think\facade\Request;
use think\App;
use think\exception\FileException;
use think\exception\ValidateException;
use think\facade\Filesystem;
use think\facade\Validate;
use think\file\UploadedFile;
use think\filesystem\Driver;
use think\helper\Str;

class FilesystemService
{
    protected App $app;

    public function __construct(App $app)
    {
        $this->app = $app;
    }

    public function getDownloadUrl($fileId)
    {
        return $this->getFilesystemApiUrl()."/i/down/".hashids(12)->encode($fileId)."?token=".md5(uniqid());
    }
    public function getFilesystemApiUrl()
    {
        $filesystem_apiUrl = $this->app->config->get('filesystem.api_url');
        if ($filesystem_apiUrl == "#") {
            $filesystem_apiUrl = Request::domain();
        } else if (!Validate::url($filesystem_apiUrl)) {
            throw new FileException('请正确配置filesystem.api_url');
        }
        return $filesystem_apiUrl;
    }
    public function getFileUrl($rule_id, $fileId, $file_path, $zlink = false)
    {
        $rule = SysFileRule::find($rule_id);
        if ($rule) {
            $putUrl = $rule->url ? $rule->url : $this->getFilesystemApiUrl();
            if($zlink){
                return $putUrl.$file_path;
            }
            // 是否需要安全访问
            if ($rule->permissions) {
                $uploadPath = "i/" . hashids(12)->encode($fileId);
                return $putUrl . '/' . ltrim($uploadPath, '/');
            }else{
                return $putUrl.$file_path;
            }
        }
        return "";
    }

    /**
     * @param UploadedFile $file 上传的文件
     * @param int $directory_id 存储的目录位置
     * @param int $user_id 用户ID
     * @param array $vars
     * @return UploadResult
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function upload(UploadedFile $file, int $directory_id, int $user_id, array $vars = []): UploadResult
    {
        $rule_id = 1;
        if ($directory_id > 0) {
            // 上传到某个目录下
            $directory = SysUserFile::where('is_directory', 1)->find($directory_id);
            if (empty($directory)) {
                throw new ValidateException('Dir: 目录不存在');
            }
        }
        /*
         * 文件记录实体
         * filesystem.model
         */
        $filesystem_model = $this->app->config->get('filesystem.model');
        if (empty($filesystem_model)) {
            throw new FileException('File记录模型未定义');
        }
        /*
         * 文件预览接口(用于安全访问模式)
         * filesystem.api_url
         */
        $filesystem_apiUrl = $this->app->config->get('filesystem.api_url');
        if (empty($filesystem_apiUrl)) {
            throw new FileException('请正确配置filesystem.api_url');
        }
        if ($filesystem_apiUrl == "#") {
            $filesystem_apiUrl = Request::domain();
        } else if (!Validate::url($filesystem_apiUrl)) {
            throw new FileException('请正确配置filesystem.api_url');
        }

        /**
         * @var SysFileRule|\app\model\SysFileRule $rule
         */
        $rule = SysFileRule::find($rule_id);
        if (empty($rule)) {
            throw new ValidateException('Rule: 上传规则不存在');
        }
        if ($rule->is_close) {
            throw new ValidateException('Rule: 已关闭上传权限');
        }
        /*
         * 根据规则校验上传的文件是否符合要求
         */
        $this->checkFileRule($rule, $file);


        $putDisk = $rule->disk;
        $putUrl = $rule->url ? $rule->url : $filesystem_apiUrl;
        $putPath = $rule->path_rule;
        $nameRule = $rule->name_rule;


        $nameRule = $this->replaceName($nameRule, $file, $vars + ['uid' => $user_id]);
        $putPath = $this->replaceName($putPath, $file, $vars + ['uid' => $user_id]);

        $savePath = ($putPath ? trim($putPath, '/') : '') . '/' . $nameRule;

        $fileName = pathinfo($savePath, PATHINFO_FILENAME);
        $filePath = pathinfo($savePath, PATHINFO_DIRNAME);

        $disk = Filesystem::disk('public');
        $uploadPath = $this->uploadFile($disk, $filePath, $file, $fileName);
        if ($uploadPath === false) {
            throw new FileException('上传失败');
        }
        $uploadPath = $disk->url($uploadPath);

        $fileUrl = $putUrl . '/' . ltrim($uploadPath, '/');

        $uploadSavePath = pathinfo($uploadPath, PATHINFO_DIRNAME);
        $fileSaveName = pathinfo($uploadPath, PATHINFO_BASENAME);
        // 系统文件上传日志记录
        $sysFileRecord = $this->app->invokeClass($filesystem_model);
        $sysFileRecord->save([
            'rule_id'  => $rule->id,
            'name'     => $fileSaveName,
            'path'     => $uploadSavePath,
            'length'   => $file->getSize(),
            'extension' => $file->getOriginalExtension(),
            'content_type' => mime_content_type($file->getRealPath()) ?: '',
            'md5' => $file->md5(),
            'disk' => $putDisk,
            'create_user_id' => $user_id,
            'context_id' => $this->app->request->contextId,
            'create_date' => date('Y-m-d'),
            'create_time' => date('Y-m-d H:i:s'),
            'update_time' => date('Y-m-d H:i:s'),
            'delete_time' => null,
        ]);


        // 是否需要安全访问
        if ($rule->permissions) {
            $fileId = hashids(12)->encode($sysFileRecord->id);
            $uploadPath = "i/" . $fileId;
            $fileUrl = $putUrl . '/' . ltrim($uploadPath, '/');
        }

        (new SysUserFile)->save([
            'rule_id' => $rule_id,
            'disk' => $putDisk,
            'user_id' => $user_id,
            'name' => $file->getOriginalName(),
            'is_directory' => 0,
            'parent_id' => $directory_id,
            'path' => $uploadPath,
            'length' => $file->getSize(),
            'content_type' => $file->getOriginalMime(),
            'deleted' => 0,
            'url' => $fileUrl,
            'is_anonymous' => 0,
        ]);

        /*
         * 上传结果封装
         */
        $results = new UploadResult([
            // 文件对外显示URL
            'url' => $fileUrl,
            // 文件对外显示存储路径
            'path' => $uploadPath,
            // 文件上传时间戳
            'time' => time(),
        ]);

        /*
         * 触发上传完成事件
         */
        $this->triggerUploaded($results, $putDisk, $file, $rule, $sysFileRecord);

        return $results;
    }


    private function replaceName(string $pathname, UploadedFile $file, $vars = []): string
    {
        $array = [
            '{Y}' => date('Y'),
            '{y}' => date('y'),
            '{m}' => date('m'),
            '{d}' => date('d'),
            '{H}' => date('H'),
            '{i}' => date('i'),
            '{s}' => date('s'),
            '{G}' => date('G'),
            '{Ym}' => date('Ym'),
            '{Ymd}' => date('Ymd'),
            '{YmdH}' => date('YmdH'),
            '{timestamp}' => time(),
            '{uniqid}' => uniqid(),
            '{md5}' => md5(microtime() . Str::random()),
            '{md5-16}' => substr(md5(microtime() . Str::random()), 0, 16),
            '{str-random-16}' => Str::random(16),
            '{str-random-10}' => Str::random(10),
            '{str-random-6}' => Str::random(6),
            '{fmd5}' => $file->md5(),
            '{fsha1}' => $file->sha1(),
            '{fname}' => $file->getOriginalName(),
            '{uid}' => $vars['uid'] ?? 'UID',
        ];
        // 系统内部的key不允许被覆盖
        $useKeys = array_keys($array);
        foreach ($vars as $key => $value) {
            if (in_array($key, $useKeys)) {
                continue;
            }
            $array["{{$key}}"] = $value;
        }
        return str_replace(array_keys($array), array_values($array), $pathname);
    }

    private function checkFileRule(SysFileRule|\app\model\SysFileRule $fileRule, UploadedFile $file): void
    {
        /*
        * 验证文件信息
        */
        if ($fileRule->rule_file_size_limit_min > 0) {
            if ($file->getSize() < $fileRule->rule_file_size_limit_min * 1024) {
                throw new ValidateException('上传限制：文件太小了');
            }
        }
        $validateRule = [];
        if ($fileRule->rule_file_size_limit_max > 0) {
            $validateRule[] = 'fileSize:' . ($fileRule->rule_file_size_limit_max * 1024);
        }
        if ($fileRule->rule_file_ext !== '') {
            $validateRule[] = "fileExt:$fileRule->rule_file_ext";
        }
        if ($fileRule->rule_file_mime !== '') {
            $validateRule[] = "fileMime:$fileRule->rule_file_mime";
        }
        if ($fileRule->rule_image !== '') {
            $validateRule[] = "image:$fileRule->rule_image";
        }
        if ($validateRule) {
            validate(['file' => implode('|', $validateRule)])
                ->check(['file' => $file]);
        }
    }

    /**
     * 触发上传完成事件
     * @param UploadResult $results 上传结果
     * @param string $putDisk 存储磁盘
     * @param UploadedFile $file 上传的文件
     * @param SysFileRule $rule 上传规则
     * @param SysFileRecord $sysFileRecord 文件上传记录
     * @return void
     */
    private function triggerUploaded(UploadResult $results, string $putDisk, UploadedFile $file, SysFileRule $rule, SysFileRecord $sysFileRecord): void
    {
        $eventData = ['results' => $results, 'file' => $file, 'rule' => $rule, 'model' => $sysFileRecord];
        // 触发文件上传事件
        Event::trigger('uploaded', $eventData);
        // 触发磁盘上传事件
        Event::trigger("uploaded.$putDisk", $eventData);
        // 触发磁盘某个规则的上传事件
        if (!empty($rule->id)) {
            Event::trigger("uploaded.rule_{$rule->id}", $eventData);
        }
    }

    private function uploadFile(Driver $driver, string $filePath, UploadedFile $file, string $fileName): bool|string
    {
        return $driver->putFile($filePath, $file, function () use ($fileName) {
            return $fileName;
        });
    }
}