<?php

declare(strict_types=1);

namespace apexl\Io\modules\file\services;

use apexl\ClassEngine\EngineSingleton;
use apexl\Config\Singleton;
use apexl\Io\includes\System;
use apexl\Io\includes\Utils;
use apexl\Io\modules\file\entities\fileEntity;
use apexl\Io\modules\user\services\currentUser;
use Exception;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use Psr\Http\Message\ServerRequestInterface as Request;

class File extends EngineSingleton
{
    public const DEFAULT_ALLOWED_TYPES = [
        'pdf',
        'jpg',
        'jpeg',
        'png',
        'doc',
        'docx',
        'xls',
        'xlsx',
        'csv',
    ];

    public static function getFileFromUpload(Request $request, string $fieldName = 'file'): fileEntity
    {
        $uploadedFile = $request->getUploadedFiles();
        $fileEntity = new fileEntity();
        $fileEntity->name = $uploadedFile[$fieldName]->getClientFilename();
        $fileEntity->mime = $uploadedFile[$fieldName]->getClientMediaType();
        $fileEntity->created = time();
        $fileEntity->created_by = currentUser::getCurrentUser()->id;
        $fileEntity->addMetadata('uploadedFile', $uploadedFile[$fieldName]);

        return $fileEntity;
    }

    /**
     * @throws Exception
     */
    public static function storeUploadedFile($uploadedFile, $directory = 'uploads', $storeByMonth = true): string
    {
        $directory = trim((string) $directory, DIRECTORY_SEPARATOR);
        if ($storeByMonth) {
            $directory .= '/'.date('d-m-Y');
        }

        return Utils::moveUploadedFile($directory, $uploadedFile);
    }

    /**
     * @throws Exception
     */
    public static function storePNGFromData(string $data, string $directory, ?string $filename = null): string
    {
        $data = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $data));
        //can't move the file if the key is not st in config, throw an error.
        $config = Singleton::getInstance();
        if (!isset($config->app->fileUploadDirectory)) {
            throw new Exception(
                'The required configuration for fileUploadDirectory '.
                '({"app": {"fileUploadDirectory": "path/to/dir"}} is not defined'
            );
        }

        $filename = $filename ?: date('d-m-Y_H:i:s').'.png';

        $basePath = System::getBasePath();
        $directory = sprintf(
            '%s%s%s',
            $config->app->fileUploadDirectory,
            DIRECTORY_SEPARATOR,
            $directory
        );

        $filePath = sprintf(
            '%s%s%s',
            $directory,
            DIRECTORY_SEPARATOR,
            $filename
        );

        Utils::createPath(
            sprintf(
                '%s%s%s',
                $basePath,
                DIRECTORY_SEPARATOR,
                $directory
            )
        );
        file_put_contents($filePath, $data);

        return $filePath;
    }

    /**
     * @param $mime
     * @param $allowedList
     * @return boolean
     * @throws Exception
     */
    public static function isTypeAllowed($mime, $allowedList): bool
    {
        //first check this is a known mimeType
        if ($ext = Utils::mime2ext($mime)) {
            return in_array($ext, $allowedList);
        } else {
            throw new Exception('Cannot verify unknown file mime: '.$mime);
        }
    }

    /**
     * Method to convert spreadsheets to csv files. Function will return one or more file entities.
     * @throws Exception
     * @todo can we avoid writing these out before returning?
     */
    public static function spreadsheet2CSV(fileEntity $file): array
    {
        $reader = new Xlsx();
        $spreadsheet = $reader->load($file->path);

        $loadedSheetNames = $spreadsheet->getSheetNames();
        $writer = new Csv($spreadsheet);
        $writer->setLineEnding("\r\n");

        $files = [];
        foreach (array_keys($loadedSheetNames) as $sheetIndex) {
            //foreach sheet we need a new file object.
            $fileEntity = new fileEntity();
            //Strip the extension from the filename and replace it with csv
            $fileEntity->name = $sheetIndex.'_'.self::nameWithoutExt($file->name).'.csv';
            $fileEntity->mime = Utils::ext2mime('csv');
            $fileEntity->created = time();
            $fileEntity->created_by = currentUser::getCurrentUser()->id;

            //we need to strip the old filename from the file path completely.
            $pathParts = explode(DIRECTORY_SEPARATOR, $file->path);
            unset($pathParts[array_key_last($pathParts)]);
            //construct the new file path by imploding the parts and adding the new csv name on the end
            $newPath = (implode(DIRECTORY_SEPARATOR, $pathParts)).DIRECTORY_SEPARATOR.$fileEntity->name;
            //write out the sheet as a CSV
            $writer->setSheetIndex($sheetIndex);
            $writer->save(System::getBasePath().DIRECTORY_SEPARATOR.$newPath);
            //Assume this worked, add the path to the new fileEntity
            $fileEntity->path = $newPath;
            //Store the record in the database
            $fileEntity->store();
            $files[] = $fileEntity;
        }

        return $files;
    }

    /**
     * Get the filename without the file extension.
     */
    public static function nameWithoutExt(string $name): string
    {
        return str_replace(
            sprintf('.%s', self::extFromName($name)),
            '',
            $name
        );
    }

    /**
     * Simple method to get the file extension from the file name.
     */
    public static function extFromName($name): string
    {
        $parts = explode('.', (string) $name);

        return array_pop($parts);
    }
}
