<?php

/**
 * @package     Joomla.API
 * @subpackage  com_media
 *
 * @copyright   (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Component\Media\Api\Model;

use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\Exception\ResourceNotFound;
use Joomla\CMS\MVC\Controller\Exception\Save;
use Joomla\CMS\MVC\Model\BaseModel;
use Joomla\Component\Media\Administrator\Exception\FileExistsException;
use Joomla\Component\Media\Administrator\Exception\FileNotFoundException;
use Joomla\Component\Media\Administrator\Exception\InvalidPathException;
use Joomla\Component\Media\Administrator\Model\ApiModel;
use Joomla\Component\Media\Administrator\Provider\ProviderManagerHelperTrait;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Media web service model supporting a single media item.
 *
 * @since  4.1.0
 */
class MediumModel extends BaseModel
{
    use ProviderManagerHelperTrait;

    /**
     * Instance of com_media's ApiModel
     *
     * @var ApiModel
     * @since  4.1.0
     */
    private $mediaApiModel;

    public function __construct($config = [])
    {
        parent::__construct($config);

        $this->mediaApiModel = new ApiModel();
    }

    /**
     * Method to get a single files or folder.
     *
     * @return  \stdClass  A file or folder object.
     *
     * @since   4.1.0
     * @throws  ResourceNotFound
     */
    public function getItem()
    {
        $options = [
            'path'    => $this->getState('path', ''),
            'url'     => $this->getState('url', false),
            'temp'    => $this->getState('temp', false),
            'content' => $this->getState('content', false),
        ];

        ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($this->getState('path', ''));

        try {
            return $this->mediaApiModel->getFile($adapterName, $path, $options);
        } catch (FileNotFoundException $e) {
            throw new ResourceNotFound(
                Text::sprintf('WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND', $path),
                404
            );
        }
    }

    /**
     * Method to save a file or folder.
     *
     * @param   string  $path  The primary key of the item (if exists)
     *
     * @return  string   The path
     *
     * @since   4.1.0
     *
     * @throws  Save
     */
    public function save($path = null): string
    {
        $path     = $this->getState('path', '');
        $oldPath  = $this->getState('old_path', '');
        $content  = $this->getState('content', null);
        $override = $this->getState('override', false);

        ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($path);

        // Trim adapter information from path
        if ($pos = strpos($path, ':/')) {
            $path = substr($path, $pos + 1);
        }

        // Trim adapter information from old path
        if ($pos = strpos($oldPath, ':/')) {
            $oldPath = substr($oldPath, $pos + 1);
        }

        $resultPath = '';

        /**
         * If we have a (new) path and an old path, we want to move an existing
         * file or folder. This must be done before updating the content of a file,
         * if also requested (see below).
         */
        if ($path && $oldPath) {
            try {
                // ApiModel::move() (or actually LocalAdapter::move()) returns a path with leading slash.
                $resultPath = trim(
                    $this->mediaApiModel->move($adapterName, $oldPath, $path, $override),
                    '/'
                );
            } catch (FileNotFoundException $e) {
                throw new Save(
                    Text::sprintf(
                        'WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND',
                        $oldPath
                    ),
                    404
                );
            }
        }

        // If we have a (new) path but no old path, we want to create a
        // new file or folder.
        if ($path && !$oldPath) {
            // com_media expects separate directory and file name.
            // If we moved the file before, we must use the new path.
            $basename = basename($resultPath ?: $path);
            $dirname  = dirname($resultPath ?: $path);

            try {
                // If there is content, com_media's assumes the new item is a file.
                // Otherwise a folder is assumed.
                $name = $content
                    ? $this->mediaApiModel->createFile(
                        $adapterName,
                        $basename,
                        $dirname,
                        $content,
                        $override
                    )
                    : $this->mediaApiModel->createFolder(
                        $adapterName,
                        $basename,
                        $dirname,
                        $override
                    );

                $resultPath = $dirname . '/' . $name;
            } catch (FileNotFoundException $e) {
                throw new Save(
                    Text::sprintf(
                        'WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND',
                        $dirname . '/' . $basename
                    ),
                    404
                );
            } catch (FileExistsException $e) {
                throw new Save(
                    Text::sprintf(
                        'WEBSERVICE_COM_MEDIA_FILE_EXISTS',
                        $dirname . '/' . $basename
                    ),
                    400
                );
            } catch (InvalidPathException $e) {
                throw new Save(
                    Text::sprintf(
                        'WEBSERVICE_COM_MEDIA_BAD_FILE_TYPE',
                        $dirname . '/' . $basename
                    ),
                    400
                );
            }
        }

        // If we have no (new) path but we do have an old path and we have content,
        // we want to update the contents of an existing file.
        if ($oldPath && $content) {
            // com_media expects separate directory and file name.
            // If we moved the file before, we must use the new path.
            $basename = basename($resultPath ?: $oldPath);
            $dirname  = dirname($resultPath ?: $oldPath);

            try {
                $this->mediaApiModel->updateFile(
                    $adapterName,
                    $basename,
                    $dirname,
                    $content
                );
            } catch (FileNotFoundException $e) {
                throw new Save(
                    Text::sprintf(
                        'WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND',
                        $dirname . '/' . $basename
                    ),
                    404
                );
            } catch (InvalidPathException $e) {
                throw new Save(
                    Text::sprintf(
                        'WEBSERVICE_COM_MEDIA_BAD_FILE_TYPE',
                        $dirname . '/' . $basename
                    ),
                    400
                );
            }

            $resultPath = $resultPath ?: $oldPath;
        }

        // If we still have no result path, something fishy is going on.
        if (!$resultPath) {
            throw new Save(
                Text::_(
                    'WEBSERVICE_COM_MEDIA_UNSUPPORTED_PARAMETER_COMBINATION'
                ),
                400
            );
        }

        return $resultPath;
    }

    /**
     * Method to delete an existing file or folder.
     *
     * @return  void
     *
     * @since   4.1.0
     * @throws  Save
     */
    public function delete(): void
    {
        ['adapter' => $adapterName, 'path' => $path] = $this->resolveAdapterAndPath($this->getState('path', ''));

        try {
            $this->mediaApiModel->delete($adapterName, $path);
        } catch (FileNotFoundException $e) {
            throw new Save(
                Text::sprintf('WEBSERVICE_COM_MEDIA_FILE_NOT_FOUND', $path),
                404
            );
        }
    }
}
