const fs = require('fs');
const nurl = require('url');
const path = require('path');
const stream = require('stream');
const request = require('request');

const ANSWER_METHODS = {
    addArticle: 'article', addPhoto: 'photo', addVideo: 'video',
    addGif: 'gif', addVideoGif: 'mpeg4_gif', addSticker: 'sticker',
    addVoice: 'voice', addDocument: 'document', addLocation: 'location',
    addVenue: 'venue', addGame: 'game',
    cachedPhoto: 'photo', cachedGif: 'gif', cachedVideoGif: 'mpeg4_gif',
    cachedSticker: 'sticker', cachedDocument: 'document', cachedVideo: 'video',
    cachedVoice: 'voice', cachedAudio: 'audio'
};

const DEFAULT_FILE_EXTENSIONS = {
    photo: 'jpg', audio: 'mp3', document: 'doc',
    sticker: 'webp', voice: 'm4a', video: 'mp4'
};

const REGEXP_URL = /^https?:\/\/|www\./;

// Methods
const methods = {

    keyboard(keyboard, opt = {}) {
        const markup = keyboard !== undefined ? {keyboard} : {};
        if (opt.resize === true) markup['resize_keyboard'] = true;
        if (opt.once === true) markup['one_time_keyboard'] = true;
        if (opt.remove === true) markup['remove_keyboard'] = true;
        if (opt.selective) markup['selective'] = opt.selective;
        return markup;
    },

    button(type, text) {
        if (!text && type) return {text: type};
        type = `request_${type}`;
        return {text, [type]: true};
    },

    inlineKeyboard(inline_keyboard) {
        return {inline_keyboard};
    },

    inlineQueryKeyboard(inline_keyboard) {
        return {inline_keyboard: inline_keyboard};
    },

    inlineButton(text, opt = {}) {
        const markup = {text};
        if (opt.url) markup.url = opt.url;
        if (opt.inline !== undefined) markup.switch_inline_query = opt.inline;
        if (opt.inlineCurrent !== undefined) markup.switch_inline_query_current_chat = opt.inlineCurrent;
        if (opt.callback) markup.callback_data = String(opt.callback);
        if (opt.game) markup.callback_game = String(opt.game);
        if (opt.pay === true) markup.pay = opt.pay;
        return markup;
    },

    answerList(id, opt) {
        return new AnswerList(id, opt);
    },

    getMe: {},

    sendMessage: {
        arguments: ['chat_id', 'text']
    },

    forwardMessage: {
        arguments: ['chat_id', 'from_chat_id', 'message_id']
    },

    sendPhoto(chat_id, photo, opt) {
        return sendFile.call(this, 'photo', photo, opt, {chat_id});
    },

    sendAudio(chat_id, audio, opt = {}) {
        const form = {chat_id};

        if (opt.title) form.title = opt.title;
        if (opt.performer) form.performer = opt.performer;
        if (Number.isInteger(opt.duration)) form.duration = opt.duration;

        return sendFile.call(this, 'audio', audio, opt, form);
    },

    sendDocument(chat_id, doc, opt) {
        return sendFile.call(this, 'document', doc, opt, {chat_id});
    },

    sendAnimation(chat_id, animation, opt) {
        return sendFile.call(this, 'animation', animation, opt, {chat_id});
    },

    sendSticker(chat_id, sticker, opt) {
        return sendFile.call(this, 'sticker', sticker, opt, {chat_id});
    },

    sendVideo(chat_id, video, opt = {}) {
        const form = {chat_id};

        if (Number.isInteger(opt.duration)) form.duration = opt.duration;
        if (Number.isInteger(opt.width)) form.width = opt.width;
        if (Number.isInteger(opt.height)) form.height = opt.height;

        if (opt.supportsStreaming) {
            form.supports_streaming = true;
        }

        return sendFile.call(this, 'video', video, opt, form);
    },

    sendVideoNote: {
        arguments: ['chat_id', 'video_note'],
        options(form, opt = {}) {
            if (Number.isInteger(opt.duration)) form.duration = opt.duration;
            if (Number.isInteger(opt.length)) form.length = opt.length;
            return form;
        }
    },

    sendVoice(chat_id, voice, opt = {}) {
        const form = {chat_id};

        if (Number.isInteger(opt.duration)) form.duration = opt.duration;

        return sendFile.call(this, 'voice', voice, opt, form);
    },

    sendLocation: {
        arguments(chat_id, position) {
            return {
                chat_id, latitude: position[0], longitude: position[1]
            };
        },
        options(form, opt = {}) {
            if (Number.isInteger(opt.livePeriod)) form.live_period = opt.livePeriod;
            return form;
        }
    },

    editMessageLiveLocation: {
        arguments(opt = {}) {
            const form = {};

            if (opt.latitude) form.latitude = opt.latitude;
            if (opt.longitude) form.longitude = opt.longitude;

            if (opt.chatId) form.chat_id = opt.chatId;
            if (opt.messageId) form.message_id = opt.messageId;
            if (opt.inlineMessageId) form.inline_message_id = opt.inlineMessageId;

            return form;
        }
    },

    stopMessageLiveLocation: {
        arguments(opt = {}) {
            const form = {};

            if (opt.chatId) form.chat_id = opt.chatId;
            if (opt.messageId) form.message_id = opt.messageId;
            if (opt.inlineMessageId) form.inline_message_id = opt.inlineMessageId;

            return form;
        }
    },

    editMessageMedia: {
        arguments(media, opt = {}) {
            const form = {};

            if (opt.chatId) form.chat_id = opt.chatId;
            if (opt.messageId) form.message_id = opt.messageId;
            if (opt.inlineMessageId) form.inline_message_id = opt.inlineMessageId;

            form.media = JSON.stringify(media);

            return form;
        }
    },

    sendVenue: {
        arguments(chat_id, position, title, address) {
            return {
                chat_id, latitude: position[0], longitude: position[1], title, address
            };
        },
        options(form, opt = {}) {
            if (opt.foursquareId) form.foursquare_id = opt.foursquareId;
            if (opt.foursquareType) form.foursquare_type = opt.foursquareType;
            return form;
        }
    },

    sendContact: {
        arguments: ['chat_id', 'phone_number', 'first_name', 'last_name']
    },

    sendChatAction: {
        short: 'sendAction',
        arguments: ['chat_id', 'action']
    },

    getUserProfilePhotos: {
        short: 'getUserPhoto',
        arguments: 'user_id',
        options(form, opt = {}) {
            if (opt.offset) form.offset = opt.offset;
            if (opt.limit) form.limit = opt.limit;
            return form;
        }
    },

    getFile(file_id) {
        return this.request(`/getFile`, {file_id}).then(file => {
            const result = file.result;
            result.fileLink = this.fileLink + result.file_path;
            return result;
        });
    },

    sendGame: {
        arguments: ['chat_id', 'game_short_name']
    },

    setGameScore: {
        arguments: ['user_id', 'score'],
        options(form, opt = {}) {
            if (opt.force) form.force = opt.force;
            if (opt.chatId) form.chat_id = opt.chatId;
            if (opt.messageId) form.message_id = opt.messageId;
            if (opt.inlineMessageId) form.inline_message_id = opt.inlineMessageId;
            if (opt.disableEditMessage) form.disable_edit_message = opt.disableEditMessage;
            return form;
        }
    },

    getGameHighScores: {
        arguments: ['user_id'],
        options(form, opt = {}) {
            if (opt.chatId) form.chat_id = opt.chatId;
            if (opt.messageId) form.message_id = opt.messageId;
            if (opt.inlineMessageId) form.inline_message_id = opt.inlineMessageId;
            return form;
        }
    },

    sendInvoice: {
        arguments: ['chat_id'],
        options(form, opt = {}) {
            const photo = opt.photo || {};
            const need = opt.need || {};

            form.title = opt.title;
            form.description = opt.description;
            form.payload = opt.payload;
            form.provider_token = opt.providerToken;
            form.start_parameter = opt.startParameter;
            form.currency = opt.currency;
            form.send_phone_number_to_provider = opt.sendPhoneNumberToProvider;
            form.send_email_to_provider = opt.sendEmailToProvider;
            form.prices = JSON.stringify(opt.prices);
            form.provider_data = JSON.stringify(opt.providerData);

            if (photo.url) form.photo_url = photo.url;
            if (Number.isInteger(photo.size)) form.photo_size = photo.size;
            if (Number.isInteger(photo.width)) form.photo_width = photo.width;
            if (Number.isInteger(photo.height)) form.photo_height = photo.height;

            if (need.name === true) form.need_name = need.name;
            if (need.phoneNumber === true) form.need_phone_number = need.phoneNumber;
            if (need.email === true) form.need_email = need.email;
            if (need.shippingAddress === true) form.need_shipping_address = need.shippingAddress;

            if (opt.isFlexible === true) form.is_flexible = opt.isFlexible;

            return form;
        }
    },

    sendMediaGroup: {
        arguments(chat_id, mediaList) {
            return {
                chat_id,
                media: JSON.stringify(mediaList)
            };
        }
    },

    getStickerSet: {
        arguments: ['name']
    },

    uploadStickerFile(user_id, sticker, opt) {
        return sendFile.call(this, 'png_sticker', sticker, opt, {user_id}, 'uploadStickerFile');
    },

    createNewStickerSet(user_id, name, title, sticker, emojis, opt = {}) {
        const form = {user_id, name, title, emojis};
        if (opt.containsMasks) form.contains_masks = opt.containsMasks;
        if (opt.maskPosition) form.mask_position = opt.maskPosition;
        return sendFile.call(this, 'png_sticker', sticker, opt, form, 'createNewStickerSet');
    },

    setChatStickerSet: {
        arguments: ['chat_id', 'sticker_set_name']
    },

    deleteChatStickerSet: {
        arguments: ['chat_id']
    },

    addStickerToSet(user_id, name, sticker, emojis, opt = {}) {
        const form = {user_id, name, emojis};
        if (opt.maskPosition) form.mask_position = opt.maskPosition;
        return sendFile.call(this, 'png_sticker', sticker, opt, form, 'addStickerToSet');
    },

    setStickerPositionInSet: {
        arguments: ['sticker', 'position']
    },

    deleteStickerFromSet: {
        arguments: ['sticker']
    },

    getChat: {
        arguments: ['chat_id']
    },

    leaveChat: {
        arguments: ['chat_id']
    },

    getChatAdministrators: {
        short: 'getAdmins',
        arguments: ['chat_id']
    },

    getChatMember: {
        short: 'getMember',
        arguments: ['chat_id', 'user_id']
    },

    getChatMembersCount: {
        short: 'countMembers',
        arguments: ['chat_id']
    },

    kickChatMember: {
        short: 'kick',
        arguments: ['chat_id', 'user_id'],
        options(form, opt) {
            if (opt.untilDate) form.until_date = opt.untilDate;
            return form;
        }
    },

    unbanChatMember: {
        short: 'unban',
        arguments: ['chat_id', 'user_id']
    },

    restrictChatMember: {
        arguments: ['chat_id', 'user_id'],
        options(form, opt) {
            if (opt.untilDate) form.until_date = opt.untilDate;
            if (opt.canSendMessages) form.can_send_messages = opt.canSendMessages;
            if (opt.canSendMediaMessages) form.can_send_media_messages = opt.canSendMediaMessages;
            if (opt.canSendOtherMessages) form.can_send_other_messages = opt.canSendOtherMessages;
            if (opt.canAddWebPagePreviews) form.can_add_web_page_previews = opt.canAddWebPagePreviews;
            return form;
        }
    },

    promoteChatMember: {
        arguments: ['chat_id', 'user_id'],
        options(form, opt) {
            if (opt.canChangeInfo) form.can_change_info = opt.canChangeInfo;
            if (opt.canPostMessages) form.can_post_messages = opt.canPostMessages;
            if (opt.canEditMessages) form.can_edit_messages = opt.canEditMessages;
            if (opt.canDeleteMessages) form.can_delete_messages = opt.canDeleteMessages;
            if (opt.canInviteUsers) form.can_invite_users = opt.canInviteUsers;
            if (opt.canRestrictMembers) form.can_restrict_members = opt.canRestrictMembers;
            if (opt.canPinMessages) form.can_pin_messages = opt.canPinMessages;
            if (opt.canPromoteMembers) form.can_promote_members = opt.canPromoteMembers;
            return form;
        }
    },

    exportChatInviteLink: {
        arguments: ['chat_id']
    },

    setChatPhoto(chat_id, photo, opt) {
        return sendFile.call(this, 'photo', photo, opt, {chat_id}, 'setChatPhoto');
    },

    deleteChatPhoto: {
        arguments: ['chat_id']
    },

    setChatTitle: {
        arguments: ['chat_id', 'title']
    },

    setChatDescription: {
        arguments: ['chat_id', 'description']
    },

    pinChatMessage: {
        arguments: ['chat_id', 'message_id']
    },

    unpinChatMessage: {
        arguments: ['chat_id']
    },

    answerInlineQuery: {
        short: 'answerQuery',
        arguments(answers) {
            const data = {
                inline_query_id: answers.id,
                results: answers.results(),
                next_offset: answers.nextOffset,
                is_personal: answers.personal,
                cache_time: answers.cacheTime
            };

            if (answers.pmText !== undefined) data.switch_pm_text = answers.pmText;
            if (answers.pmParameter !== undefined) data.switch_pm_parameter = answers.pmParameter;

            return data;
        }
    },

    answerCallbackQuery: {
        short: 'answerCallback',
        arguments: ['callback_query_id'],
        options(form, opt) {
            if (opt.text) form.text = opt.text;
            if (opt.url) form.url = opt.url;
            if (opt.showAlert) form.show_alert = opt.showAlert;
            if (opt.cacheTime) form.cache_time = opt.cacheTime;
            return form;
        }
    },

    answerShippingQuery: {
        arguments: ['shipping_query_id', 'ok'],
        options(form, opt) {
            if (opt.shippingOptions) form.shipping_options = opt.shippingOptions;
            if (opt.errorMessage) form.error_message = opt.errorMessage;
            return form;
        }
    },

    answerPreCheckoutQuery: {
        arguments: ['pre_checkout_query_id', 'ok'],
        options(form, opt) {
            if (opt.errorMessage) form.error_message = opt.errorMessage;
            return form;
        }
    },

    deleteMessage: {
        arguments: ['chat_id', 'message_id']
    },

    editMessageText: {
        short: 'editText',
        arguments: (obj, text) => editObject(obj, {text})
    },

    editMessageCaption: {
        short: 'editCaption',
        arguments: (obj, caption) => editObject(obj, {caption})
    },

    editMessageReplyMarkup: {
        short: 'editMarkup',
        arguments: (obj, reply_markup) => editObject(obj, {reply_markup})
    },

    setWebhook(url, certificate, allowedUpdates, maxConnections) {
        const form = {url};

        if (Array.isArray(allowedUpdates)) {
            form.allowed_updates = allowedUpdates;
        }

        if (Number.isInteger(maxConnections)) {
            form.max_connections = maxConnections;
        }

        if (certificate) {
            form.certificate = {
                value: fs.readFileSync(certificate),
                options: {
                    filename: 'cert.pem'
                }
            };
            return this.request('/setWebhook', null, form);
        }

        return this.request('/setWebhook', form);
    },

    getWebhookInfo: {},

    deleteWebhook: {},

    sendDice: {
        arguments: ['chat_id', 'emoji']
    }
};

// Functions

function editObject(obj, form) {
    if (obj.chatId && obj.messageId) {
        form.chat_id = obj.chatId;
        form.message_id = obj.messageId;
    } else if (obj.inlineMsgId) {
        form.inline_message_id = obj.inlineMsgId;
    }
    return form;
}

function sendFile(type, file, opt = {}, methodForm = {}, methodUrl) {

    const form = this.properties(methodForm, opt);
    const defaultName = `file.${DEFAULT_FILE_EXTENSIONS[type]}`;

    const url = methodUrl ? methodUrl : 'send' + type.charAt(0).toUpperCase() + type.slice(1);

    // Send bot action event
    this.event(url, [].slice.call(arguments).splice(0, 1));

    // Set file caption
    if (opt.caption) form.caption = opt.caption;

    if (file instanceof stream.Stream) {
        // File stream object
        if (!opt.fileName) {
            opt.fileName = nurl.parse(path.basename(file.path)).pathname;
        }
        form[type] = {
            value: file,
            options: {filename: opt.fileName}
        };
    } else if (Buffer.isBuffer(file)) {
        // File buffer
        if (!opt.fileName) opt.fileName = defaultName;
        form[type] = {
            value: file,
            options: {filename: opt.fileName}
        };
    } else if (REGEXP_URL.test(file)) {
        // File url
        if (!opt.fileName) {
            opt.fileName = path.basename(nurl.parse(file).pathname) || defaultName;
        }
        if (opt.serverDownload === true) {
            // Download by our server
            form[type] = {
                value: request.get(file),
                options: {filename: opt.fileName}
            };
        } else {
            // Download by Telegram server
            form[type] = file;
        }
    } else if (fs.existsSync(file)) {
        // File location
        if (!opt.fileName) opt.fileName = path.basename(file);
        form[type] = {
            value: fs.createReadStream(file),
            options: {filename: opt.fileName}
        };
    } else {
        // File as 'file_id'
        form[type] = file;
    }

    return this.request(`/${url}`, null, form).then((response) => response.result);

}

/* Answer List */

class AnswerList {

    constructor(id, opt = {}) {
        this.id = id;
        this.cacheTime = Number(opt.cacheTime) || 300;
        this.nextOffset = opt.nextOffset === undefined ? null : opt.nextOffset;
        this.personal = opt.personal === undefined ? false : opt.personal;
        this.pmText = opt.pmText;
        this.pmParameter = opt.pmParameter;
        this.list = [];
    }

    add(type, set = {}) {
        set.type = type;
        this.list.push(set);
        return set;
    }

    results() {
        return JSON.stringify(this.list);
    }

}

// Add answer methods
{
    for (let prop in ANSWER_METHODS) {
        AnswerList.prototype[prop] = (name => {
            return function (set) {
                return this.add(name, set);
            };
        })(ANSWER_METHODS[prop]);
    }
}

// Export methods
module.exports = methods;
