<?php

namespace App\Domains\File\Services;

use Carbon\Carbon;
use App\Core\Service;
use Illuminate\Http\Request;
use App\Models\Account\Account;
use Illuminate\Http\UploadedFile;
use App\Exceptions\BeUsedException;
use Intervention\Image\Facades\Image;
use App\Models\File\File as FileModel;
use App\Exceptions\NotAllowedException;
use Illuminate\Support\Facades\Storage;
use App\Exceptions\UploadFailedException;
use App\Domains\File\Repositories\FileRepository;
use App\Models\File\FileWith as FileFileWithModel;
use App\Domains\File\Repositories\FileWithRepository;

class FileService extends Service
{
    protected $user;

    protected $fileRepository;

    protected $fileWithRepository;

    public static $types = ['account/avatar'];

    public static $mimeTypes = [
        'image' => ['image/jpeg', 'image/gif', 'image/bmp', 'image/png'],
        'audio' => ['audio/amr', 'audio/aac', 'audio/ogg', 'audio/mpeg'],
        'video' => ['video/mp4', 'video/mpeg', 'video/x-flv', 'video/3gpp', 'video/quicktime', 'video/mov','video/x-m4v']
    ];

    public static $extensions = [
        'image' => ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
        'audio' => ['amr', 'ogg', 'mpeg'],
        'video' => ['mp4', 'mpeg', 'flv', '3gp', 'mov']
    ];

    /**
     * 构造函数
     *
     * @return void
     */
    public function __construct(Request $request, FileRepository $fileRepository, FileWithRepository $fileWithRepository)
    {
        $this->user = $request->user();
        $this->fileRepository = $fileRepository;
        $this->fileWithRepository = $fileWithRepository;
    }

    /**
     * 文件列表
     *
     * @param array $conditions
     * @return mixed
     */
    public function index(array $conditions = [])
    {
        $limit = $conditions['limit'] ?? 20;
        return $this->fileRepository->with('file_withs')->applyConditions()->paginate($limit);
    }

    /**
     * 删除
     *
     * @return bool
     */
    public function destroy($ids)
    {
        $ids = is_array($ids) ? $ids : [$ids];

        if ($this->fileWithRepository->whereIn('file_id', $ids)->count()) {
            throw new BeUsedException('文件已被使用， 不能删除');
        }

        $this->fileRepository->destroy($ids);

        return true;
    }

    /**
     * 使用type, type_id查找文件
     *
     * @param string $type
     * @param string|int $typeIds
     * @return void
     */
    public function fetchByType($type, $typeIds, $force = false)
    {
        if ($force) {
            $this->fileWithRepository->forgetCached();
        }

        $files =  $this->fileWithRepository->with('file')->withType($type, $typeIds)->get();

        $files->map(function ($item) {
            if ($file = $item->file) {
                $file->url = cdn($item->disk, $item->filename, $item->created_at);
                $file->cover = $item->thumb ? cdn($item->disk, $this->generateFilePath($item->filename), $item->created_at) : '';
                $item->file = $file;
            }
        });

        return $files;
    }

    /**
     * 储存上传文件
     *
     * @param UploadedFile $file
     *
     * @throws UploadFailedException
     * @return mixed
     */
    public function upload(UploadedFile $file, $input = [], $disk = null)
    {
        $disk = $disk ?: config('filesystems.default');

        if ($input['filename']) {
            $fileModel = $this->store($file, $input, $disk, true);
        } else {
            $fileModel = $this->validateFileInDatabase($file, function (UploadedFile $file, string $md5) use ($input, $disk) {
                $input['filename'] = $this->generateFilePath($md5);
                return $this->store($file, $input, $disk);
            });

            if ($input['type'] && $input['typeid'] && in_array($input['type'], self::$types)) {
                $this->apply($fileModel->id, $input['type'], $input['typeid'], $input['cover'] ?? false);
            }
        }


        return $fileModel;
    }

    /**
     * 添加文件关联
     *
     * @param array|int $ids
     * @param string $type
     * @param int|string $typeid
     * @return void
     */
    public function apply($ids, $type, $typeid, $cover = false)
    {
        if (!in_array($type, self::$types)) {
            throw new NotAllowedException('不允许的上传类型');
        }

        $ids = is_array($ids) ? $ids : [$ids];

        if (empty($ids)) {
            return false;
        }

        if ($this->user instanceof Account) {
            $column = 'account_id';
        }

        $news = [];

        $existed_ids = $this->fileRepository->select('id')->whereIn('id', $ids)->get()->pluck('id')->toArray();

        foreach ($ids as $id) {
            if (!in_array($id, $existed_ids)) {
                continue;
            }

            $new = [
                'file_id' => $id,
                'account_id' => 0,
                'type' => $type,
                'type_id' => $typeid,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s'),
            ];

            if ($column) {
                $new[$column] = $this->user->id;
            }

            $news[] = $new;
        }

        if (empty($news)) {
            return false;
        }

        if ($cover) {
            $this->detach($type, $typeid);
        }

        return $this->fileWithRepository->insert($news);
    }

    /**
     * 取消文件关联
     *
     * @param string $type
     * @param int|string $typeid
     * @return boolean
     */
    public function detach($type, $typeid)
    {
        $this->fileWithRepository->withType($type, $typeid)->delete();
        return true;
    }

    /**
     * 缩略图文件名
     *
     * @param string $filename
     * @return string
     */
    public static function getThumb($filename)
    {
        return $filename.'.thumb.jpg';
    }

    /**
     * 验证文件是否已上传过，返回FileModel实例
     *
     * @param UploadedFile $file
     * @param callable $call
     * @return FileModel
     */
    protected function validateFileInDatabase(UploadedFile $file, callable $call): ?FileModel
    {
        $hash = md5_file($file->getRealPath());

        return $this->fileRepository->where('hash', $hash)->firstOr(function () use ($file, $call, $hash) {
            return call_user_func_array($call, [$file, $hash]);
        });
    }

    /**
     * 存储文件
     *
     * @param UploadedFile $file
     * @param array $input
     * @param string $disk
     * @return mixed
     */
    protected function store(UploadedFile $file, $input, $disk, $cover = false)
    {
        $disk = $disk ?: config('filesystems.default');
        $clientHeight = $input['height'] ?? 0;
        $clientWidth = $input['width'] ?? 0;
        $clientDuration = $input['duration'] ?? 0;

        $hash = md5_file($file->getRealPath());
        $path = $input['filename'] ?? $this->generateFilePath($hash);

        // 图片做旋转处理
        if (in_array($file->getClientMimeType(), ['image/jpeg', 'image/bmp', 'image/png'])) {
            ini_set('memory_limit', '-1');
            try {
                Image::make($file->getRealPath())->orientate()->save($file->getRealPath(), 100);
            } catch (\Exception $e) {
            }
        }

        list($width, $height) = ($imageInfo = @getimagesize($file->getRealPath())) === false ? [null, null] : $imageInfo;

        if (($filename = $file->storeAs($path, '', $disk)) === false) {
            throw new UploadFailedException('上传失败');
        }

        $new = [
            'hash' => $hash,
            'disk' => $disk,
            'filename' => $filename,
            'origin_filename' => $file->getClientOriginalName(),
            'mimetype' => $file->getClientMimeType(),
            'filesize' => $file->getClientSize(),
            'width' =>  $width ?? $clientWidth,
            'height' => $height ?? $clientHeight,
            'duration' => $clientDuration,
        ];

        $fileModel = $cover ? new FileModel($new) : $this->fileRepository->create($new);

        return $fileModel;
    }

    /**
     * 根据文件md5生成文件路径
     *
     * @param string $hash
     * @return void
     */
    protected function generateFilePath($hash)
    {
        return Carbon::now()->format('Y/m/d') . $hash;
    }
}
