'use strict';

const api = require('../api.js');
const router = require('../router.js');
const uri = require('../util/uri.js');
const misc = require('../util/misc.js');
const progress = require('../util/progress.js');
const topNavigation = require('../models/top_navigation.js');
const Post = require('../models/post.js');
const Tag = require('../models/tag.js');
const PostUploadView = require('../views/post_upload_view.js');
const EmptyView = require('../views/empty_view.js');

const genericErrorMessage =
    'One of the posts needs your attention; ' +
    'click "resume upload" when you\'re ready.';

class PostUploadController {
    constructor() {
        this._lastCancellablePromise = null;

        if (!api.hasPrivilege('posts:create')) {
            this._view = new EmptyView();
            this._view.showError('You don\'t have privileges to upload posts.');
            return;
        }

        topNavigation.activate('upload');
        topNavigation.setTitle('Upload');
        this._view = new PostUploadView({
            canUploadAnonymously: api.hasPrivilege('posts:create:anonymous'),
            canViewPosts: api.hasPrivilege('posts:view'),
            enableSafety: api.safetyEnabled(),
        });
        this._view.addEventListener('change', e => this._evtChange(e));
        this._view.addEventListener('submit', e => this._evtSubmit(e));
        this._view.addEventListener('cancel', e => this._evtCancel(e));
    }

    _evtChange(e) {
        if (e.detail.uploadables.length) {
            misc.enableExitConfirmation();
        } else {
            misc.disableExitConfirmation();
            this._view.clearMessages();
        }
    }

    _evtCancel(e) {
        if (this._lastCancellablePromise) {
            this._lastCancellablePromise.abort();
        }
    }

    _evtSubmit(e) {
        this._view.disableForm();
        this._view.clearMessages();

        e.detail.uploadables.reduce(
            (promise, uploadable) =>
                promise.then(() => this._uploadSinglePost(
                    uploadable, e.detail.skipDuplicates)),
            Promise.resolve())
                .then(() => {
                    this._view.clearMessages();
                    misc.disableExitConfirmation();
                    const ctx = router.show(uri.formatClientLink('posts'));
                    ctx.controller.showSuccess('Posts uploaded.');
                }, error => {
                    if (error.uploadable) {
                        if (error.similarPosts) {
                            error.uploadable.lookalikes = error.similarPosts;
                            this._view.updateUploadable(error.uploadable);
                            this._view.showInfo(genericErrorMessage);
                            this._view.showInfo(
                                error.message, error.uploadable);
                        } else {
                            this._view.showError(genericErrorMessage);
                            this._view.showError(
                                error.message, error.uploadable);
                        }
                    } else {
                        this._view.showError(error.message);
                    }
                    this._view.enableForm();
                });
    }

    _uploadSinglePost(uploadable, skipDuplicates) {
        progress.start();
        let reverseSearchPromise = Promise.resolve();
        if (!uploadable.lookalikesConfirmed) {
            reverseSearchPromise =
                Post.reverseSearch(uploadable.url || uploadable.file);
        }
        this._lastCancellablePromise = reverseSearchPromise;

        return reverseSearchPromise.then(searchResult => {
            if (searchResult) {
                // notify about exact duplicate
                if (searchResult.exactPost) {
                    if (skipDuplicates) {
                        this._view.removeUploadable(uploadable);
                        return Promise.resolve();
                    } else {
                        let error = new Error('Post already uploaded ' +
                            `(@${searchResult.exactPost.id})`);
                        error.uploadable = uploadable;
                        return Promise.reject(error);
                    }
                }

                // notify about similar posts
                if (searchResult.similarPosts.length) {
                    let error = new Error(
                        `Found ${searchResult.similarPosts.length} similar ` +
                        'posts.\nYou can resume or discard this upload.');
                    error.uploadable = uploadable;
                    error.similarPosts = searchResult.similarPosts;
                    return Promise.reject(error);
                }
            }

            // no duplicates, proceed with saving
            let post = this._uploadableToPost(uploadable);
            let savePromise = post.save(uploadable.anonymous)
                .then(() => {
                    this._view.removeUploadable(uploadable);
                    return Promise.resolve();
                });
            this._lastCancellablePromise = savePromise;
            return savePromise;
        }).then(result => {
            progress.done();
            return Promise.resolve(result);
        }, error => {
            error.uploadable = uploadable;
            progress.done();
            return Promise.reject(error);
        });
    }

    _uploadableToPost(uploadable) {
        let post = new Post();
        post.safety = uploadable.safety;
        post.flags = uploadable.flags;
        for (let tagName of uploadable.tags) {
            const tag = new Tag();
            tag.names = [tagName];
            post.tags.add(tag);
        }
        post.relations = uploadable.relations;
        post.newContent = uploadable.url || uploadable.file;
        // if uploadable.source is ever going to be a valid field (e.g when setting source directly in the upload window)
        // you'll need to change the line below to `post.source = uploadable.source || uploadable.url;`
        if (uploadable.url) post.source = uploadable.url;
        return post;
    }
}

module.exports = router => {
    router.enter(['upload'], (ctx, next) => {
        ctx.controller = new PostUploadController();
    });
};