<?php
declare (strict_types = 1);

namespace app\admin\controller\v1;

use app\admin\model\v1\SysImageUploadRecordModel;
use app\admin\model\v1\SysVideoUploadRecordModel;
use app\admin\model\v1\SysAttachmentUploadRecordModel;
use Intervention\Image\ImageManager;
use Intervention\Image\Typography\FontFactory;
use think\facade\Filesystem;
use filesystem\Qiniu;

/**
 * 文件上传控制器
 */
class Upload
{
    // 上传图片
    public function image()
    {
        $param = request()->param(['module' => 'unknown']);
        if (is_null($param)) {
            return error('请确定请求参数正确');
        }
        $file = request()->file('image');
        if (is_null($file)) {
            return error('请确定上传对象或字段是否正确');
        }

        try {
            $max_size = strtobytes(env('ADMIN_API.MAX_IMAGE_SIZE', '1mb'));
            $validate = validate([
                'module' => 'require|max:64',
                'image'  => "fileSize:$max_size|fileExt:jpg,jpeg,png,gif,webp"
            ]);
            if (!$validate->check(['module' => $param['module'], 'image' => $file])) {
                return error($validate->getError());
            }

            $storage = config('filesystem.disks.image.url');

            $filemd5  = $file->md5();
            $filesha1 = $file->sha1();

            // 获取图片上传配置
            list(
                'filename_keep'  => $filename_keep,
                'filemd5_unique' => $filemd5_unique,
                'filetype_to'    => $filetype_to,
            ) = $this->getUploadOptions('upload_image');

            // 获取文件大小
            $file_size = $file->getSize();
            // 获取文件mime类型
            $mime_type = $file->getOriginalMime();

            // 是否需要根据文件MD5值检查文件是否已存在
            $image_model = $filemd5_unique ? SysImageUploadRecordModel::md5($filemd5)->find() : null;
            if (is_null($image_model)) {
                // 检查是否需要保留原文件名生成器
                $name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null;

                // 处理图片
                $image_manager = ImageManager::gd();
                if ($filetype_to == 'original') {
                    $filename = Filesystem::disk('image')->putFile($param['module'], $file, $name_rule());
                    $image    = $image_manager->read('.' . $storage . '/' . $filename);
                }
                else if ($filetype_to == 'webp') {
                    $image = $image_manager->read($file->getRealPath());
                    // 转换为webp格式
                    $webp     = $image->toWebp(75);
                    $root     = config('filesystem.disks.image.root');
                    $filename = $param['module'] . '/' . ($name_rule() ? $name_rule()() : date('Ymd') . '/' . md5((string)time() . random_str(8))) . '.webp';
                    $webp->save($this->checkPath($root . '/' . $filename));
                    // 获取webp文件大小
                    $file_size = $webp->size();
                    // 获取webp文件mime类型
                    $mime_type = $webp->mimetype();
                }

                // 水印
                list(
                    'enabled'       => $enabled,
                    'type'          => $type,
                    'text_options'  => $text_options,
                    'image_options' => $image_options
                ) = $this->getWatermarkOptions();
                if ($enabled) {
                    // 图片水印
                    if ($type == 'IMAGE' && $image_options['image'] != '') {
                        // 读取水印图片
                        $watermark_image = $image_manager->read(public_path() . $image_options['image']);
                        // 缩放水印图片
                        $watermark_image->scale($image_options['width'], $image_options['height']);
                        // 绘制水印图片
                        $image->place(
                            $watermark_image, 
                            $image_options['position'], 
                            $image_options['offset_x'], 
                            $image_options['offset_y'], 
                            $image_options['opacity']
                        );
                    }
                    // 文字水印
                    else if ($type == 'TEXT' && $text_options['txt'] != '') {
                        // 原图宽度
                        $origin_width  = $image->width();
                        // 原图高度
                        $origin_height = $image->height();

                        $font_factory = new FontFactory(function(FontFactory $font) use($text_options) {
                            // 设置字体
                            $font->filename(public_path() . $text_options['font']);
                            // 设置字体大小
                            $font->size($text_options['size']);
                            // 设置字体颜色及透明度
                            $opacity = $text_options['opacity'] > 0 ? dechex((int)ceil(255 * ($text_options['opacity'] / 100))) : '00';
                            $font->color($text_options['color'] . $opacity);
                            $font->align('left');
                            $font->valign('top');
                        });
                        // 文字尺寸
                        $font_rect = $image->driver()->fontProcessor()->boxSize($text_options['txt'], $font_factory());
                        // 计算偏移量
                        list($offset_x, $offset_y) = $this->scaleTxtOffsetXYByPosition(
                            $text_options['position'],
                            $text_options['offset_x'],
                            $text_options['offset_y'],
                            $origin_width,
                            $origin_height,
                            $font_rect->width(),
                            $font_rect->height()
                        );
                        // 绘制文字
                        $image->text(
                            $text_options['txt'], 
                            $offset_x, 
                            $offset_y, 
                            $font_factory()
                        );
                    }
                    $image->save('.'. $storage. '/'. $filename);
                }

                // 缩略图
                $image->scale(200, 200);
                $idx = strrpos($filename, '.');
                $thumb_filename = mb_substr($filename, 0, $idx) . '_thumb.' . mb_substr($filename, $idx + 1);
                $image->save('.' . $storage . '/' . $thumb_filename);
                
                // 保存图片
                $image_model              = new SysImageUploadRecordModel();
                $image_model->language_id = request()->lang_id;
                $image_model->module      = $param['module'];
                $image_model->image_path  = $storage . '/' . $filename;
                $image_model->image_thumb = $storage . '/' . $thumb_filename;
                $image_model->file_size   = $file_size;
                $image_model->file_type   = $mime_type;
                $image_model->file_md5    = $filemd5;
                $image_model->file_sha1   = $filesha1;
                if (!$image_model->save()) {
                    return error('上传失败');
                }
            }

            return success('操作成功', [
                'path'       => $image_model->image_path,
                'thumb_path' => $image_model->image_thumb,
                'filemd5'    => $image_model->file_md5,
                'filesha1'   => $image_model->file_sha1
            ]);
        } catch (\Throwable $th) {
            return error($th->getMessage());
        }

        return error('上传失败');
    }
    /**
     * 检查路径
     * 
     * @param string $path
     * @return string
     */
    private function checkPath($path): string
    {
        $ok       = true;
        $filename = basename($path);
        $dirname  = dirname($path);
        if (!is_dir($dirname)) {
            $ok = @mkdir($dirname, 0755, true);
        }
        else if (!is_writable($dirname)) {
            $ok = @chmod($dirname,0755);
        }

        if ($ok) {
            return $dirname . '/' . $filename;
        }

        throw new \Exception("上传目标目录不可用");
    }
    /**
     * 文件名生成回调
     *
     * @param \think\file\UploadedFile $file
     * @return callable
     */
    private function filenameGenerator(\think\file\UploadedFile $file): callable
    {
        return fn() => date('Ymd') . '/' . pathinfo($file->getOriginalName(), PATHINFO_FILENAME);
    }
    /**
     * 获取上传配置
     *
     * @param string $module
     * @return array
     */
    private function getUploadOptions($module)
    {
        $config_model = new \app\admin\controller\v1\SiteConfig;
        $config       = $config_model->getByGroupUniqueLabel('upload');
        $options      = data_get($config, $module, []);
        
        return [
            'filename_keep'  => (int)data_get($options, 'filename_keep.value', 0)  == 1,
            'filemd5_unique' => (int)data_get($options, 'filemd5_unique.value', 0) == 1,
            'filetype_to'    => data_get($options, 'filetype_to.value', 'original'),
            'save_to'        => data_get($options, 'save_to.value', 'local'),
        ];
    }
    /**
     * 获取水印配置
     *
     * @return array
     */
    private function getWatermarkOptions(): array
    {
        $config_model     = new \app\admin\controller\v1\SiteConfig;
        $watermark_config = $config_model->getByGroupUniqueLabel('watermark');

        $opacity = data_get($watermark_config, 'watermark_opacity.value', 100);
        if ($opacity == '') {
            $opacity = 100;
        }
        return [
            'enabled'      => data_get($watermark_config, 'watermark_enabled.value', 0) == 1,
            'type'         => data_get($watermark_config, 'watermark_type.value', ''),
            'text_options' => [
                'txt'      => data_get($watermark_config, 'watermark_text_value.value', ''),
                'font'     => data_get($watermark_config, 'watermark_text_font.value', ''),
                'size'     => (float)data_get($watermark_config, 'watermark_text_size.value', 12)?:12,
                'color'    => data_get($watermark_config, 'watermark_text_color.value', '#000000')?:'#000000',
                'position' => data_get($watermark_config, 'watermark_position.value', 'top-left')?:'top-left',
                'offset_x' => (int)data_get($watermark_config, 'watermark_offset_x.value', 0),
                'offset_y' => (int)data_get($watermark_config, 'watermark_offset_y.value', 0),
                'opacity'  => (int)$opacity,
            ],
            'image_options' => [
                'image'    => data_get($watermark_config, 'watermark_image_value.value', ''),
                'width'    => (int)data_get($watermark_config, 'watermark_image_width.value')?:null,
                'height'   => (int)data_get($watermark_config, 'watermark_image_height.value')?:null,
                'position' => data_get($watermark_config, 'watermark_position.value', 'top-left')?:'top-left',
                'offset_x' => (int)data_get($watermark_config, 'watermark_offset_x.value', 0),
                'offset_y' => (int)data_get($watermark_config, 'watermark_offset_y.value', 0),
                'opacity'  => (int)$opacity,
            ]
        ];
    }
    /**
     * 计算文本水印偏移量
     *
     * @param string $position
     * @param integer $offset_x
     * @param integer $offset_y
     * @param integer $image_width
     * @param integer $image_height
     * @param integer $txt_width
     * @param integer $txt_height
     * @return array
     */
    private function scaleTxtOffsetXYByPosition(string $position, int $offset_x, int $offset_y, int $image_width, int $image_height, int $txt_width, int $txt_height)
    {
        switch ($position) {
            case 'top-left':
                // top-left:左上角
                return [$offset_x, $offset_y];
            case 'top-right':
                // top-right:右上角
                return [(int)($image_width-$txt_width-$offset_x), $offset_y];
            case 'top':
                // top:上 - 水平居中
                return [(int)(($image_width-$txt_width+$offset_x)/2), $offset_y];
            case 'left':
                // left:左 - 垂直居中
                return [$offset_x, (int)(($image_height-$txt_height)/2+$offset_y)];
            case 'center':
                // center:水平垂直居中
                return [(int)(($image_width-$txt_width)/2+$offset_x), (int)(($image_height-$txt_height)/2+$offset_y)];
            case 'right':
                // right:右 - 垂直居中
                return [(int)($image_width-$txt_width-$offset_x), (int)(($image_height-$txt_height)/2+$offset_y)];
            case'bottom':
                // bottom:下 - 水平居中
                return [(int)(($image_width-$txt_width+$offset_x)/2), (int)($image_height-$txt_height-$offset_y)];
            case'bottom-left':
                // bottom-left:左下角
                return [$offset_x, (int)($image_height-$txt_height-$offset_y)];
            case'bottom-right':
                // bottom-right:右下角
                return [(int)($image_width-$txt_width-$offset_x), (int)($image_height-$txt_height-$offset_y)];
            default:
                throw new \InvalidArgumentException('Invalid position');
        }
    }

    /**
     * 上传视频
     */
    public function video()
    {
        $param = request()->param(['module' => 'unknown']);
        if (is_null($param)) {
            return error('请确定请求参数正确');
        }
        $file = request()->file('video');
        if (is_null($file)) {
            return error('请确定上传对象或字段是否正确');
        }

        try {
            $max_size = strtobytes(env('ADMIN_API.MAX_VIDEO_SIZE', '100mb'));
            $validate = validate([
                'module' => 'require|max:64',
                'video'  => "fileSize:$max_size|fileExt:mp4"
            ]);
            if (!$validate->check(['module' => $param['module'], 'video' => $file])) {
                return error($validate->getError());
            }

            $storage = config('filesystem.disks.video.url');

            $filemd5  = $file->md5();
            $filesha1 = $file->sha1();

            // 获取视频上传配置
            list(
                'filename_keep'  => $filename_keep,
                'filemd5_unique' => $filemd5_unique,
                'save_to'        => $save_to,
            ) = $this->getUploadOptions('upload_video');
            // 是否需要根据文件MD5值检查文件是否已存在
            $video = $filemd5_unique ? SysVideoUploadRecordModel::md5($filemd5)->find() : null;
            if (is_null($video)) {
                // 保存位置配置 key
                $disk = 'video';
                // 检查是否需要保留原文件名
                $name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null;

                // 保存到七牛云
                if ($save_to == 'qiniu_cloud') {
                    $disk      = 'video_qiniu';
                    $storage   = config('filesystem.disks.video_qiniu.path_prefix');
                }
                $filename  = Filesystem::disk($disk)->putFile($param['module'], $file, $name_rule());

                // 组装视频路径
                $video_path = $storage . '/' . $filename;
                if ($save_to == 'qiniu_cloud') {
                    $video_path = Filesystem::disk($disk)->url($filename);
                }

                // 保存视频
                $video              = new SysVideoUploadRecordModel();
                $video->language_id = request()->lang_id;
                $video->module      = $param['module'];
                $video->video_path  = $video_path;
                $video->file_size   = $file->getSize();
                $video->file_type   = $file->getOriginalMime();
                $video->file_md5    = $filemd5;
                $video->file_sha1   = $filesha1;
                if (!$video->save()) {
                    return error('上传失败');
                }
            }

            return success('上传成功', [
                'path'      => $video->video_path,
                'file_md5'  => $video->file_md5,
                'file_sha1' => $video->file_sha1
            ]);
        } catch (\Throwable $th) {
            return error($th->getMessage());
        }

        return error('上传失败');
    }

    /**
     * 附件上传
     */
    public function attachment()
    {
        $file = request()->file('attachment');
        if (is_null($file)) {
            return error('请确定上传对象或字段是否正确');
        }

        try {
            $max_size = strtobytes(env('ADMIN_API.MAX_ATTACHMENT_SIZE', '100mb'));
            $validate = validate([
                'attachment'  => "fileSize:$max_size|fileExt:biz,bz,bz2,gz,tgz,zip,rar,7z,doc,docx,xls,xlsx,csv,ppt,pptx,pdf,txt,jpg,jpeg,png,webp,ttf"
            ]);
            if (!$validate->check(['attachment' => $file])) {
                return error($validate->getError());
            }

            $storage = config('filesystem.disks.public.url');

            $filemd5  = $file->md5();
            $filesha1 = $file->sha1();

            // 获取附件上传配置
            list(
                'filename_keep'  => $filename_keep,
                'filemd5_unique' => $filemd5_unique,
                'save_to'        => $save_to
            ) = $this->getUploadOptions('upload_attachment');
            // 是否需要根据文件MD5值检查文件是否已存在
            $attachment = $filemd5_unique ? SysAttachmentUploadRecordModel::md5($filemd5)->find() : null;
            if (is_null($attachment)) {
                // 保存位置配置 key
                $disk = 'public';
                // 检查是否需要保留原文件名
                $name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null;

                // 保存到七牛云
                if ($save_to == 'qiniu_cloud') {
                    $disk      = 'public_qiniu';
                    $storage   = config('filesystem.disks.public_qiniu.path_prefix');
                }
                $filename  = Filesystem::disk($disk)->putFile('attachments', $file, $name_rule());

                // 组装附件路径
                $attachment_path = $storage . '/' . $filename;
                if ($save_to == 'qiniu_cloud') {
                    $attachment_path = Filesystem::disk($disk)->url($filename);
                }

                // 保存附件
                $attachment                  = new SysAttachmentUploadRecordModel();
                $attachment->language_id     = request()->lang_id;
                $attachment->attachment_path = $attachment_path;
                $attachment->file_size       = $file->getSize();
                $attachment->file_type       = $file->getOriginalMime();
                $attachment->file_md5        = $filemd5;
                $attachment->file_sha1       = $filesha1;
                if (!$attachment->save()) {
                    return error('上传失败');
                }
            }

            return success('上传成功', [
                'path'      => $attachment->attachment_path,
                'file_md5'  => $attachment->file_md5,
                'file_sha1' => $attachment->file_sha1
            ]);
        } catch (\Throwable $th) {
            return error($th->getMessage());
        }

        return error('上传失败');
    }
}
