<?php

namespace Ignite\Core\Models\Import;

use Ignite\Core\Contracts\Repositories\ImportRepository;
use Illuminate\Support\Facades\Storage;

class File
{
    /**
     * @var string
     */
    protected $type;

    /**
     * @var string
     */
    protected $label;

    /**
     * @var string
     */
    protected $file;

    /**
     * @var string
     */
    protected $disk;

    /**
     * @var string
     */
    protected $log;

    /**
     * Create a new instance from the provided request.
     *
     * @param \Illuminate\Http\Request $request
     * @param array $allowedTypes
     * @param string $disk
     * @return static
     */
    public static function fromRequest($request, $allowedTypes = [], $disk = 'local')
    {
        $type = $request->get('type');
        $label = $allowedTypes[$type];
        $files = self::makeFilePaths($label, $disk);

        // Seems like this should use $request instead of request().
        $file = request()->file('file')
            ->storeAs(dirname($files['file']), basename($files['file']), [
                'disk' => $disk,
                'visibility' => 'public',
            ]);

        return new static($type, $label, $file, $files['log'], $disk);
    }

    /**
     * Create an instance based on a file on a filesystem.
     *
     * @param string $fromPath The path to the file.
     * @param string $type The type of import. See \Ignite\Core\Contracts\Repositories\ImportRepository::getAllowedTypes()
     * @param string|null $fromDisk The filesystem the file is on (defaults to config('filesystems.default')).
     * @return static
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     * @throws \Illuminate\Contracts\Filesystem\FileExistsException
     */
    public static function fromPath(string $fromPath, string $type, ?string $fromDisk = null): File
    {
        /** @var ImportRepository $importRepository */
        $importRepository = app(ImportRepository::class);
        $allowedTypes = $importRepository->getAllowedTypes();

        if (! array_key_exists($type, $allowedTypes)) {
            throw new \InvalidArgumentException("Cannot make import file. Given type does not exist: $type");
        }

        $disk = config('core.import.disk', 'local');
        $label = $allowedTypes[$type];
        $importsFolder = config('core.import.directory', 'imports');
        if ($fromDisk === $disk && dirname($fromPath) === $importsFolder && pathinfo($fromPath, PATHINFO_EXTENSION) === 'csv') {
            // Given a file in the imports directory already.
            $files = ['file' => $fromPath, 'log' => dirname($fromPath).'/'.basename($fromPath, '.csv').'.log'];
        } else {
            $files = self::makeFilePaths($label);
            $fromStream = Storage::disk($fromDisk)->readStream($fromPath);
            Storage::disk($disk)->writeStream($files['file'], $fromStream, [
                'visibility' => 'public',
            ]);
        }

        return new static($type, $label, $files['file'], $files['log'], $disk);
    }

    /**
     * Create an instance based on a filesystem File instance.
     *
     * @param \Ignite\Core\Files\File $file
     * @param string $type
     * @return File
     * @throws \Illuminate\Contracts\Filesystem\FileExistsException
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    public static function fromFile(\Ignite\Core\Files\File $file, string $type): File
    {
        return static::fromPath($file->getRelativePath(), $type, $file->getDisk());
    }

    /**
     * Convert this import file to a generic file.
     *
     * @return \Ignite\Core\Files\File
     */
    public function toFile(): \Ignite\Core\Files\File
    {
        return new \Ignite\Core\Files\File($this->getFile(), $this->getDisk());
    }

    /**
     * Makes a filename for a file import of the given type.
     *
     * @param string $typeLabel
     * @param string|null $disk
     * @return array{file: string, log: string}
     * #[ArrayShape(['file' => 'string', 'log' => 'string'])]
     */
    private static function makeFilePaths(string $typeLabel, ?string $disk = null): array
    {
        $disk = $disk ?? 'local'; // We could make this a config value (core.import.disk).
        $folder = config('core.import.directory', 'imports');
        $timestamp = now()->format('YmdHis');

        $i = 0;
        do {
            $filename = snake_case($typeLabel).'-'.$timestamp.($i++ ?: '');

            $file = $folder.DIRECTORY_SEPARATOR.$filename.'.csv';
            $log = $folder.DIRECTORY_SEPARATOR.$filename.'.log';
        } while (Storage::disk($disk)->exists($file));

        return compact('file', 'log');
    }

    /**
     * ImportFile constructor.
     *
     * @param string $type
     * @param $label
     * @param string $file
     * @param string $disk
     */
    public function __construct($type, $label, $file, $log, $disk)
    {
        $this->type = $type;
        $this->file = $file;
        $this->log = $log;
        $this->disk = $disk;
        $this->label = $label;
    }

    /**
     * Cast the object to an array.
     *
     * @return array
     */
    public function toArray()
    {
        return [
            'type' => $this->type,
            'label' => $this->label,
            'file' => $this->file,
            'log' => $this->log,
            'disk' => $this->disk,
        ];
    }

    /**
     * Get the import type.
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Get the import type label.
     *
     * @return string
     */
    public function getLabel()
    {
        return $this->label;
    }

    /**
     * Get the import file path.
     *
     * @return string
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * Get the import log path.
     *
     * @return string
     */
    public function getLog()
    {
        return $this->log;
    }

    /**
     * Get the import file path.
     *
     * @return string
     */
    public function getDisk()
    {
        return $this->disk;
    }
}
