<?php

namespace Mtc\Plugins\Clinic\Src;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Mtc\Plugins\Clinic\Src\Models\PatientFile;

class Uploader
{
    /**
     * @var array params. Possible parameters:
     *
     * - allowed_formats - array of extensions, e.g., ['doc', 'pdf', 'xls']
     * - max_file_size - maximum file size in bytes
     * - module - Model class to link the file to
     * - id - Model ID to link the file to
     * - public - boolean value for whether the file will be accessible via URL or not
     * - type - File type. To be able to tell what this file is for
     * - title - File title. Some files with same type will need title for recognition purposes
     *
     */
    public $params = [];
    public $errors = [];

    // Default max upload file size: 16 MB
    const MAX_FILE_SIZE = 16 * 1024 * 1024;

    const ALLOWED_FORMATS = [
        'doc' => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'png' => 'image/png',
        'pdf' => 'application/pdf',
        'txt' => 'text/plain',
        'xls' => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'csv' => 'text/csv',
    ];

    /**
     * Uploader constructor.
     *
     * Sets file upload parameters
     *
     * @param array $params
     */
    public function __construct($params = [])
    {
        $this->params['max_file_size'] = ! empty($params['max_file_size'])
            ? $params['max_file_size']
            : self::MAX_FILE_SIZE;
        $this->params['allowed_formats'] = ! empty($params['allowed_formats'])
            ? $params['allowed_formats']
            : array_keys(self::ALLOWED_FORMATS);
        $this->params['disk'] = ! empty($params['public']) ? 'public' : 'local';
        $this->params['type'] = ! empty($params['type']) ? $params['type'] : 'file';
        $this->params['title'] = $params['title'] ?? null;
    }

    /**
     * Uploads file. Returns information about the uploaded file: name, path, size, extension
     *
     * @param UploadedFile $file
     * @return false|Builder|Model
     */
    public function uploadFile(UploadedFile $file)
    {
        $this->errors = [];

        if (! $this->fileValid($file)) {
            return false;
        }

        $originalName = $file->getClientOriginalName();
        $sanitizedName = Str::slug(pathinfo($originalName, PATHINFO_FILENAME), '_');
        if (empty($sanitizedName)) {
            // If the original file name has no latin chars / numbers at all,
            // we just create a random string as name
            $sanitizedName = Str::random(10);
        }
        $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        $fileName = time().'_'.Str::random(8).'_'.$sanitizedName.'.'.$extension;
        $uploadPath = $this->getUploadPath();
        $file->storeAs($uploadPath, $fileName, $this->params['disk']);

        return PatientFile::query()
            ->create([
                'module' => $this->params['module'] ?? null,
                'module_id' => $this->params['id'] ?? null,
                'type' => $this->params['type'],
                'title' => $this->params['title'],
                'name' => $sanitizedName,
                'full_name' => $fileName,
                'extension' => $extension,
                'path' => $this->params['type'].'/'.$fileName,
                'size' => $file->getSize(),
                'disk' => $this->params['disk'],
            ]);
    }

    /**
     * Validates file against params
     *
     * @param UploadedFile $file
     * @return bool
     */
    private function fileValid(UploadedFile $file): bool
    {
        // Validate Max File Size
        if ($file->getSize() > $this->params['max_file_size']) {
            $this->errors[] = 'File size exceeds maximum of '.$this->params['max_file_size'].' bytes';

            return false;
        }

        // Validate extension and file type
        $fileExtension = strtolower($file->getClientOriginalExtension());
        if (! in_array($fileExtension, $this->params['allowed_formats'], false)) {
            $this->errors[] = 'File extension '.$fileExtension.' not supported';

            return false;
        }

        // Validate Mime Type
        $fileMimeType = $file->getMimeType();
        if (! empty(self::ALLOWED_FORMATS[$fileExtension]) && $fileMimeType !== self::ALLOWED_FORMATS[$fileExtension]) {
            // If the extension is mapped with a mime type,
            // but the uploaded file has a mime type that differs from the mapped one,
            // we won't tolerate it

            $this->errors[] = 'File extension does not match the mime type: '.$fileMimeType;

            return false;
        }

        return true;
    }

    /**
     * Gets upload path based on client ID and upload folder
     *
     * @return string
     */
    private function getUploadPath(): string
    {
        return $this->params['type'].'/';
    }

    /**
     * Whether the uploaded file is an image (and can be resized)
     *
     * @param $extension
     * @return bool
     */
    private function isImage($extension): bool
    {
        return in_array($extension, ['jpg', 'jpeg', 'png'], false);
    }
}
