<?php
/**
 * @author: Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 * @version: 09.10.2024
 */

namespace Mtc\Plugins\Clinic\Src\Models;

use App\Casts\OptionalEncrypted;
use Carbon\Carbon;
use Exception;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Http\UploadedFile;
use Mtc\Modules\Members\Classes\Auth;
use Mtc\Plugins\Clinic\Src\Clinic;
use Mtc\Plugins\Clinic\Src\Logger;
use Mtc\Plugins\Clinic\Src\Uploader;
use Mtc\Plugins\Wisebee\Classes\Models\WisebeeDocument;
use Mtc\Shop\Assessment\Question;
use stdClass;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\File;

/**
 * Accessors
 *
 * @property int $id
 * @property int $member_id
 * @property string $type
 * @property string $title
 * @property string $name
 * @property string $full_name
 * @property string $extension
 * @property string $path
 * @property int $size
 * @property string $disk
 *
 * @property Carbon $created_at
 * @property Carbon $updated_at
 *
 */
class PatientFile extends Model
{
    protected $table = 'patient_files';

    protected $fillable = [
        'member_id',
        'type',
        'title',
        'name',
        'full_name',
        'extension',
        'path',
        'size',
        'disk',
        'date_provided',
    ];

    protected $casts = [
        'title' => OptionalEncrypted::class,
        'name' => OptionalEncrypted::class,
        'full_name' => OptionalEncrypted::class,
        'path' => OptionalEncrypted::class,
    ];

    const IMAGE_EXTENSIONS = [
        'jpeg',
        'jpg',
        'png',
        'svg'
    ];

    const FILE_TYPE_BP_TRACK = 'bp_image';
    const FILE_TYPE_PATIENT_ID = 'id_image';
    const FILE_TYPE_SELFIE = 'selfie_image';
    const FILE_TYPE_FILE = 'file';
    const FILE_TYPE_WISEBEE_DOCUMENT = 'wisebee_document';

    public static array $types = [
        self::FILE_TYPE_FILE => 'File',
        self::FILE_TYPE_BP_TRACK => 'BP track photo',
        self::FILE_TYPE_PATIENT_ID => 'Patient ID photo',
        self::FILE_TYPE_SELFIE => 'Selfie',
        self::FILE_TYPE_WISEBEE_DOCUMENT => 'Wisebee Document',
    ];

    /**
     * Deletes a single file and any image copies
     *
     * @return bool
     */
    public function deleteFile(): bool
    {
        $path = self::getFullPath($this);

        try {
            if (!config('clinic.keep_deleted_files')) {
                if (Storage::disk($this->disk)->exists($path)) {
                    Storage::disk($this->disk)->delete($path);
                }
            }
            $this->delete();
        } catch (Exception) {

        }

        return true;
    }

    /**
     * Deletes instance files of one type
     *
     * @param $module
     * @param $id
     * @param $type
     * @throws Exception
     */
    public static function deleteFiles($module, $id, $type): void
    {
        self::query()
            ->where('module', '=', $module)
            ->where('module_id', '=', $id)
            ->where('type', '=', $type)
            ->get()
            ->each(function (self $file) {
                $file->deleteFile();
            });
    }

    /**
     * Gets full path of the file, including file name
     *
     * @return string
     */
    public static function getFullPath($file): string
    {
        return $file->type . '/' . $file->full_name;
    }

    /**
     * When passing base64 files in request, we want them to become real files
     * so that they can be processed the same way the other files are
     *
     * @param string $base64
     * @return UploadedFile
     */
    public static function createImageFileFromBase64(string $base64): UploadedFile
    {
        $fileData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64));
        $extension = explode('/', mime_content_type($base64))[1];

        $tmpFilePath = sys_get_temp_dir() . '/' . Str::uuid()->toString() . '.' . $extension;
        file_put_contents($tmpFilePath, $fileData);

        $tmpFile = new File($tmpFilePath);

        return new UploadedFile(
            $tmpFile->getPathname(),
            $tmpFile->getFilename(),
            $tmpFile->getMimeType(),
            0,
            false
        );
    }

    /**
     * Simple validator
     *
     * @param array $input
     * @return array
     */
    public static function validate(array $input): array
    {
        $required = [
            'title',
            'date_provided',
        ];

        $errors = [];

        foreach ($required as $field) {
            if (empty($input[$field])) {
                $errors[$field] = 'This field is required';
            }
        }

        return $errors;
    }

    /**
     * Injects Wisebee documents into the list of files
     *
     * @param Collection $patientFiles
     * @param $memberID
     * @return \Illuminate\Support\Collection
     */
    public static function injectWisebeeDocuments(Collection $patientFiles, $memberID)
    {
        $wisebeeDocuments = WisebeeDocument::getPatientDocuments($memberID)
            ->map(function (WisebeeDocument $wisebeeDocument) {
                $wisebeeDocument->date_provided = $wisebeeDocument->created_at;
                $wisebeeDocument->fileURL = $wisebeeDocument->file;
                $wisebeeDocument->extension = strtolower(pathinfo($wisebeeDocument->file, PATHINFO_EXTENSION));
                $wisebeeDocument->type = self::FILE_TYPE_WISEBEE_DOCUMENT;
                return $wisebeeDocument;
            });

        return $patientFiles->merge($wisebeeDocuments)
            ->sortByDesc('date_provided')
            ->values();
    }

    /**
     * Checks if the assessment has files and stores them
     *
     * @param array $uploadedFiles
     * @return void
     */
    public static function checkAndStoreFromAssessment(array $uploadedFiles): void
    {
        $member = Auth::getLoggedInMember();
        if (empty($member->id)) {
            // No member to store files against
            return;
        }

        foreach ($uploadedFiles as $questionID => $uploadedFile) {
            $question = Question::query()
                ->find($questionID);
            if (empty($question)) {
                continue;
            }

            if ($question->subtype === Question::TYPE_BP_IMAGE) {
                // BP image is saved together with BP data
                continue;
            }

            try {
                $title = Question::$question_types[$question->question_type];
                $type = $question->question_type;
                if (!empty(Question::$question_subtypes[$type]) && !empty($question->subtype)) {
                    $title = Question::$question_subtypes[$type][$question->subtype];
                    $type = $question->subtype;
                }
                if (!in_array($type, array_keys(self::$types))) {
                    $type = self::FILE_TYPE_FILE;
                }

                $uploadParams = [
                    'public' => false,
                    'type' => $type,
                    'title' => $title,
                ];
                $uploader = new Uploader($uploadParams);
                $file = $uploader->uploadFile($uploadedFile);
                $file->update([
                    'member_id' => $member->id,
                    'date_provided' => Carbon::now()->format('Y-m-d'),
                ]);

                (new Logger($file, Clinic::getActor(), $member->id))
                    ->log(Logger::FILE, Logger::ACTION_CREATED, $file->toArray());
            } catch (Exception) {}
        }
    }

    /**
     * Downloads a file. Accepts either file model instance or an object with file details
     *
     * @param PatientFile|stdClass $file
     * @return Application|ResponseFactory|Response|BinaryFileResponse
     */
    public static function downloadFile(self | stdClass $file)
    {
        $path = self::getFullPath($file);

        if (!Storage::disk($file->disk)->exists($path)) {
            return response('Not Found', 404);
        }

        return response()->download(
            Storage::disk($file->disk)->path($path),
            $file->name . '.' . $file->extension,
            [],
            'inline'
        );
    }
}
