<?php

namespace App\Services;

use App\Models\Document;
use App\Models\Feed;
use App\Models\Folder;
use App\Models\Group;
use App\Models\Highlight;
use App\Models\IgnoredFeed;
use App\Models\User;
use Illuminate\Http\Request;

/**
 * Imports data in Cyca.
 */
class Importer
{
    /**
     * Adapter used to import data.
     *
     * @var \App\Contracts\ImportAdapter
     */
    protected $importAdapter;

    /**
     * User to import data for.
     *
     * @var \App\Models\User
     */
    protected $forUser;

    /**
     * Folder to import bookmarks and feeds to.
     *
     * @var \App\Models\Folder
     */
    protected $inFolder;

    /**
     * Should we import highlights as well ?
     *
     * @var bool
     */
    protected $withHighlights = true;

    /**
     * Data to be imported as an array.
     *
     * @var array
     */
    protected $dataArray = [];

    /**
     * Group to import data to.
     *
     * @var \App\Models\Group
     */
    protected $inGroup;

    /**
     * Indicates which adapter to use for importation.
     *
     * @param string $adapterName
     *
     * @return self
     */
    public function using($adapterName)
    {
        $className = config(sprintf('importers.adapters.%s.adapter', $adapterName));

        if (empty($className)) {
            abort(422, sprintf('Unknown import adapter %s', $className));
        }

        $this->importAdapter = new $className();

        return $this;
    }

    /**
     * Defines which user to import data for. If not defined, user will be
     * extracted from request.
     *
     * @return self
     */
    public function forUser(User $user)
    {
        $this->forUser = $user;

        return $this;
    }

    /**
     * Defines the group to import data to.
     *
     * @return self
     */
    public function inGroup(Group $group)
    {
        $this->inGroup = $group;

        return $this;
    }

    /**
     * Defines in which folder data will be imported to. If not defined, root
     * folder attached to specified user will be used.
     *
     * @return self
     */
    public function inFolder(Folder $folder)
    {
        $this->inFolder = $folder;

        return $this;
    }

    /**
     * Should we ignore highlights during import ? By default, highlights will
     * be imported as well.
     *
     * @return self
     */
    public function withoutHighlights()
    {
        $this->withHighlights = false;

        return $this;
    }

    /**
     * Import data from specified file. Must be a valid json file, valid from
     * Cyca's architecture point of view.
     *
     * @param string $path Full path to file to import
     *
     * @return self
     */
    public function fromFile($path)
    {
        if (empty($this->forUser)) {
            abort(422, 'Target user not specified');
        }

        $contents = file_get_contents($path);

        if (empty($contents)) {
            abort(422, 'File does not exists');
        }

        $this->dataArray = json_decode($contents, true);

        return $this;
    }

    /**
     * Import data using current request informations.
     *
     * @return self
     */
    public function fromRequest(Request $request)
    {
        if (empty($this->importAdapter)) {
            $this->using($request->input('importer'));
        }

        if (empty($this->importAdapter)) {
            abort(422, 'An import adapter must be specified');
        }

        if (empty($this->forUser)) {
            $this->forUser = $request->user();
        }

        $this->dataArray = $this->importAdapter->importFromRequest($request);

        return $this;
    }

    /**
     * Perform the import.
     */
    public function import()
    {
        if (empty($this->inGroup)) {
            $this->inGroup = $this->forUser->groups()->wherePivot('status', '=', 'own')->first();
        }

        if (empty($this->inFolder)) {
            $this->inFolder = $this->inGroup->folders()->ofType('root')->first();
        }

        if ($this->withHighlights && !empty($this->dataArray['highlights'])) {
            $this->importHighlights($this->dataArray['highlights']);
        }

        if (!empty($this->dataArray['bookmarks'])) {
            $this->importBookmarks($this->dataArray['bookmarks']);
        }
    }

    /**
     * Import highlights from specified array.
     *
     * @param array $highlights
     */
    protected function importHighlights($highlights)
    {
        foreach ($highlights as $highlightData) {
            $highlight = Highlight::where('user_id', $this->forUser->id)->where('expression', $highlightData['expression'])->first();

            if (!$highlight) {
                $highlight = new Highlight();

                $highlight->user_id    = $this->forUser->id;
                $highlight->expression = $highlightData['expression'];
                $highlight->color      = $highlightData['color'];

                $highlight->save();
            }
        }
    }

    /**
     * Import bookmarks from specified array.
     *
     * @param array $bookmarks
     */
    protected function importBookmarks($bookmarks)
    {
        $this->importDocuments($this->inFolder, $bookmarks['documents'] ?: []);
        $this->importFolders($this->inFolder, $bookmarks['folders'] ?: []);
    }

    /**
     * Import folders.
     *
     * @param \App\Models\Folder Destination folder
     * @param array $foldersData Array of sub-folders definitions
     * @param mixed $folder
     */
    protected function importFolders($folder, $foldersData)
    {
        foreach ($foldersData as $folderData) {
            $children = $this->inGroup->folders()->save(new Folder([
                'title'     => $folderData['title'],
                'parent_id' => $folder->id,
                'user_id'   => $this->forUser->id,
            ]));

            $this->importDocuments($children, $folderData['documents']);
            $this->importFolders($children, $folderData['folders']);
        }
    }

    /**
     * Import documents.
     *
     * @param \App\Models\Folder Destination folder
     * @param array $documentsData Array of documents definitions
     * @param mixed $folder
     */
    protected function importDocuments($folder, $documentsData)
    {
        foreach ($documentsData as $docData) {
            if (empty($docData['url'])) {
                continue;
            }

            $url      = urldecode($docData['url']);
            $document = Document::firstOrCreate(['url' => $url]);

            if (array_key_exists('feeds', $docData)) {
                $this->importFeeds($document, $docData['feeds']);
            }

            $folder->documents()->save($document, [
                'initial_url' => $url,
            ]);
        }
    }

    /**
     * Import feeds.
     *
     * @param \App\Models\Document $document  Destination document
     * @param array                $feedsData Array of feeds definitions
     */
    protected function importFeeds($document, $feedsData)
    {
        $feedsToAttach = $document->feeds()->get()->pluck('id')->all();

        foreach ($feedsData as $feedData) {
            if (empty($feedData['url'])) {
                continue;
            }

            $feedUrl = urldecode($feedData['url']);

            $feed = Feed::firstOrCreate(['url' => $feedUrl]);

            $feedsToAttach[] = $feed->id;

            if ($feedData['is_ignored']) {
                $ignoredFeed = IgnoredFeed::where('user_id', $this->forUser->id)->where('feed_id', $feed->id)->first();

                if (!$ignoredFeed) {
                    $ignoredFeed = new IgnoredFeed();

                    $ignoredFeed->user()->associate($this->forUser);
                    $ignoredFeed->feed()->associate($feed);

                    $ignoredFeed->save();
                }
            }
        }

        $document->feeds()->sync($feedsToAttach);
    }
}
