import axios, { AxiosRequestConfig } from 'axios';
import FormData from 'form-data';
import { ReadStream } from 'fs';
import { makeApiAdapter } from '../adapter';
import {
    ChatMessage,
    Event,
    GroupConfig,
    GroupMemberInfo,
    MIRAI_API_HTTP_VERSION,
    NudgeKind,
    PostGroupAnnouncement,
    ResponseCode,
    SingleMessage,
    SyncMessage,
} from '../mirai';
import { sleep } from '../utils';

type UploadType = 'friend' | 'group' | 'temp';

/**
 * mirai-api-http 提供的 http adapter<br/>
 * @see <a href="https://github.com/project-mirai/mirai-api-http/blob/master/docs/adapter/HttpAdapter.md">文档<a/>
 */
const HttpApiAdapter = makeApiAdapter({
    id: 'http',
    supportVersion: MIRAI_API_HTTP_VERSION,
    configMeta: {
        qq: {
            type: Number,
            description: 'mirai-api-http服务的QQ号',
        },
        verifyKey: {
            type: String,
            required: true,
            description: 'mirai-api-http配置中的verifyKey',
        },
        host: {
            type: String,
            required: true,
            description: 'mirai-api-http服务的主机地址',
            default: () => '127.0.0.1',
        },
        port: {
            type: Number,
            required: true,
            description: 'mirai-api-http服务的端口号',
            default: () => 23333,
        },
        interval: {
            type: Number,
            description: 'http轮询周期(毫秒)',
            default: () => 500,
        },
        ssl: {
            type: Boolean,
            description: '是否使用SSL',
            default: () => false,
        },
    },
    data: {
        session: null,
    },
    methods: {
        async request<T>(uri: string, data: object, method: 'GET' | 'POST', requireSession: boolean = true, multipart: boolean = false): Promise<T> {
            // 构造axios请求config
            const config: AxiosRequestConfig = {
                method,
                url: `http${this.configs.ssl ? 's' : ''}://${this.configs.host}:${this.configs.port}/${uri}`,
                headers: {},
            };

            // 判断请求类型
            if (method === 'POST') config.data = data;
            else config.params = data;
            if (multipart) config.headers = (data as FormData).getHeaders();
            // 判断是否需要session
            if (requireSession) {
                if (!this.session) {
                    await this.verify();
                    await this.bind();
                    if (!this.session) this.logger.error('session未绑定或已失效');
                }
                config.headers.sessionKey = this.session;
            }

            // 发送请求
            try {
                const resp = (await axios.request<T>(config)).data;
                if ('code' in resp && resp['code'] !== ResponseCode.Success) {
                    this.logger.warn(`发送的请求未能达到预期的效果, 请求uri: ${uri}, 错误原因: ${(resp as any).msg}, 请求原始数据:`, data);
                }
                return resp;
            } catch (err) {
                this.logger.error('发送请求错误, 请求原始数据:', data, err.message);
            }
        },
        async get<T>(uri: string, params?: object, requireSession: boolean = true, multipart: boolean = false): Promise<T> {
            return this.request(uri, params, 'GET', requireSession, multipart);
        },
        async post<T>(uri: string, data?: object, requireSession: boolean = true, multipart: boolean = false): Promise<T> {
            return this.request(uri, data, 'POST', requireSession, multipart);
        },
    },
    async verify() {
        return new Promise((resolve, reject) => {
            this.post('verify', {verifyKey: this.configs.verifyKey}, false).then((res) => {
                if (res.code === ResponseCode.WrongVerifyKey) this.logger.error(`认证失败, 错误的verifyKey`);
                else this.session = res.session;
                resolve(res);
            }).catch((err) => {
                reject(err);
            });
        });
    },
    async bind() {
        return this.post('bind', {sessionKey: this.session, qq: this.configs.qq}, false);
    },
    async release() {
        return new Promise((resolve, reject) => {
            this.post('release', {sessionKey: this.session, qq: this.configs.qq}, false).then((res) => {
                this.session = null;
                resolve(res);
            }).catch((err) => {
                reject(err);
            });
        });
    },
    async listen() {
        if (!this.listening) {
            let first = true;
            while (this.listening || first) {
                try {
                    const resp = await this.get('fetchMessage', {count: 30});
                    if (resp.code === ResponseCode.Success) {
                        if (first) {
                            this.listening = true;
                            this.logger.info('已启动监听');
                            this.emit('listen');
                        }
                        for (const i of resp.data) {
                            if (i.type.endsWith('Message')) this.emit('message', i as ChatMessage);
                            else if (i.type.endsWith('SyncMessage')) this.emit('sync', i as SyncMessage);
                            else this.emit('event', i as Event);
                        }
                        await sleep(this.configs.interval);
                    } else {
                        this.logger.error(`监听器启动失败, 错误原因: ${(resp as any).msg}`);
                    }
                } catch (err) {
                    this.logger.error('监听时发生错误:', err.message);
                    this.listening = false;
                } finally {
                    first = false;
                }
            }
        }
    },
    stop() {
        if (this.listening) {
            this.listening = false;
            this.logger.info('已停止监听');
            this.emit('stop');
        }
    },
    async getAbout() {
        return this.get('about', null, false);
    },
    async getMessageFromId(messageId: number) {
        return this.get('messageFromId', {id: messageId});
    },
    async getBotProfile() {
        return this.get('botProfile');
    },
    async getFriendList() {
        return this.get('friendList');
    },
    async getGroupList() {
        return this.get('groupList');
    },
    async getMemberList(groupId: number) {
        return this.get('memberList', {target: groupId});
    },
    async getFriendProfile(friendId: number) {
        return this.get('friendProfile', {target: friendId});
    },
    async getMemberProfile(memberId: number, groupId: number) {
        return this.get('memberProfile', {target: groupId, memberId});
    },
    async getUserProfile(userId: number) {
        return this.get('userProfile', {target: userId});
    },
    async sendFriendMessage(friendId: number, messageChain: SingleMessage[], quoteMessageId?: number) {
        return this.post('sendFriendMessage', {
            target: friendId,
            messageChain,
            quote: quoteMessageId,
        });
    },
    async sendGroupMessage(groupId: number, messageChain: SingleMessage[], quoteMessageId?: number) {
        return this.post('sendGroupMessage', {
            target: groupId,
            messageChain,
            quote: quoteMessageId,
        });
    },
    async sendTempMessage(memberId: number, groupId: number, messageChain: SingleMessage[], quoteMessageId?: number) {
        return this.post('sendTempMessage', {
            qq: memberId,
            group: groupId,
            messageChain,
            quote: quoteMessageId,
        });
    },
    async sendNudge(targetId: number, subjectId: number, kind: NudgeKind) {
        return this.post('sendNudge', {target: targetId, subject: subjectId, kind});
    },
    async recall(messageId: number) {
        return this.post('recall', {target: messageId});
    },
    async getFileList(directoryId: string, directoryPath: string, groupId: number, friendId: number, offset: number = 1, size: number = 100, withDownloadInfo: boolean = true) {
        return this.get('file/list', {
            id: directoryId,
            path: directoryPath,
            group: groupId,
            qq: friendId,
            offset,
            size,
            withDownloadInfo,
        });
    },
    async getFileInfo(fileId: string, path: string, groupId: number, friendId: number, withDownloadInfo: boolean = true) {
        return this.get('file/info', {id: fileId, path: path, group: groupId, qq: friendId, withDownloadInfo});
    },
    async createFileDirectory(parentDirectoryId: string, parentDirectoryPath: string, directoryName: string, groupId: number, friendId: number) {
        return this.post('file/mkdir', {
            id: parentDirectoryId,
            path: parentDirectoryPath,
            group: groupId,
            qq: friendId,
            directoryName,
        });
    },
    async deleteFile(id: string, path: string, groupId: number, friendId: number) {
        return this.post('file/delete', {id, path, group: groupId, qq: friendId});
    },
    async moveFile(id: string, path: string, groupId: number, friendId: number, moveToDirectoryId: string, moveToDirectoryPath: string) {
        return this.post('file/move', {
            id,
            path,
            group: groupId,
            qq: friendId,
            moveTo: moveToDirectoryId,
            moveToPath: moveToDirectoryPath,
        });
    },
    async renameFile(id: string, path: string, groupId: number, friendId: number, name: string) {
        return this.post('file/rename', {id, path, group: groupId, qq: friendId, renameTo: name});
    },
    async deleteFriend(friendId: number) {
        return this.post('deleteFriend', {target: friendId});
    },
    async muteMember(memberId: number, groupId: number, time: number) {
        return this.post('mute', {target: groupId, memberId, time});
    },
    async unmuteMember(memberId: number, groupId: number) {
        return this.post('unmute', {target: groupId, memberId});
    },
    async kickMember(memberId: number, groupId: number, message: string) {
        return this.post('kick', {target: groupId, memberId, msg: message});
    },
    async quitGroup(groupId: number) {
        return this.post('quit', {target: groupId});
    },
    async muteAll(groupId: number) {
        return this.post('muteAll', {target: groupId});
    },
    async unmuteAll(groupId: number) {
        return this.post('unmuteAll', {target: groupId});
    },
    async setEssence(messageId: number) {
        return this.post('setEssence', {target: messageId});
    },
    async getGroupConfig(groupId: number) {
        return this.get('groupConfig', {target: groupId});
    },
    async setGroupConfig(groupId: number, config: GroupConfig) {
        return this.post('groupConfig', {target: groupId, config});
    },
    async getMemberInfo(memberId: number, groupId: number) {
        return this.get('memberInfo', {target: groupId, memberId});
    },
    async setMemberInfo(memberId: number, groupId: number, info: GroupMemberInfo) {
        return this.post('memberInfo', {target: groupId, memberId, info});
    },
    async setMemberAdmin(memberId: number, groupId: number, admin: boolean = true) {
        return this.post('memberAdmin', {target: groupId, memberId, assign: admin});
    },
    async getGroupAnnouncements(groupId: number, offset: number, size: number) {
        return this.get('anno/list', {id: groupId, offset, size});
    },
    async postGroupAnnouncement(groupId: number, announcement: PostGroupAnnouncement) {
        return this.post('anno/publish', {target: groupId, ...announcement});
    },
    async deleteGroupAnnouncement(groupId: number, announcementId: number) {
        return this.post('anno/delete', {id: groupId, fid: announcementId});
    },
    async handleNewFriendRequest(eventId: number, fromId: number, groupId: number, operate: number, message: string) {
        return this.post('resp/newFriendRequestEvent', {eventId, fromId, groupId, operate, message});
    },
    async handleMemberJoinRequest(eventId: number, fromId: number, groupId: number, operate: number, message: string) {
        return this.post('resp/memberJoinRequestEvent', {eventId, fromId, groupId, operate, message});
    },
    async handleBotInvitedJoinGroupRequest(eventId: number, fromId: number, groupId: number, operate: number, message: string) {
        return this.post('resp/botInvitedJoinGroupRequestEvent', {
            eventId,
            fromId,
            groupId,
            operate,
            message,
        });
    },
    async executeCommand(command: SingleMessage[]) {
        return this.post('cmd/execute', {command});
    },
    async registerCommand(name: string, alias: string[], usage: string, description: string) {
        return this.post('cmd/register', {name, alias, usage, description});
    },
    async uploadImage(uploadType: UploadType, imageData: ReadStream) {
        const data = new FormData();
        data.append('type', uploadType);
        data.append('img', imageData);
        return this.post('uploadImage', data, true, true);
    },
    async uploadVoice(uploadType: UploadType, voiceData: ReadStream) {
        const data = new FormData();
        data.append('type', uploadType);
        data.append('voice', voiceData);
        return this.post('uploadVoice', data, true, true);
    },
    async uploadGroupFile(uploadType: UploadType, path: string, fileData: ReadStream) {
        const data = new FormData();
        data.append('type', uploadType);
        data.append('path', path);
        data.append('file', fileData);
        return this.post('uploadFile', data, true, true);
    },
});

export = HttpApiAdapter;
