import { Dict, tryDo, isHttpError, assertOk } from "./util";
import got, { Method } from "got";

export interface RequestError {
    code: string;
    message: string;
    params: { key: string; value: unknown; }[];
}

export type RequestErrorSet = { errors: RequestError[] };

export class XenforoClient {

    constructor(private endpoint: string, private userKey: string) {
    }

    private async makeRequest<TResult, TData>(uri: string, method: Method, data?: TData): Promise<TResult> {
        const result = await tryDo(got<TResult | RequestErrorSet>(`${this.endpoint}/${uri}`, {
            responseType: "json",
            method: method,
            headers: {
                "XF-Api-Key": this.userKey
            },
            form: data
        }));

        if (!result.ok) {
            if (isHttpError(result.error))
                throw result.error.response.body as RequestErrorSet;
            else
                throw { errors: [{ code: "UNK", message: "Unkown error" }] } as RequestErrorSet;
        } else {
            return result.result?.body as TResult;
        }
    }

    async getMe(): Promise<User> {
        const { me }: {me: User} = await assertOk(this.makeRequest("me/", "get"));
        return me;
    }

    async postReply(thread_id: number, message: string, attachment_key?: string): Promise<void> {
        return await assertOk(this.makeRequest("posts/", "post", {
            thread_id,
            message,
            attachment_key
        }));
    }

    async editThread(id: number, opts?: EditThreadOptions): Promise<CreateThreadResponse> {
        return await assertOk(this.makeRequest(`threads/${id}`, "post", opts));
    }

    async editPost(id: number, opts?: EditPostOptions): Promise<EditPostResponse> {
        return await assertOk(this.makeRequest(`posts/${id}`, "post", opts));
    }

    async getThread(id: number, opts?: GetThreadOptions): Promise<GetThreadResponse> {
        return await assertOk(this.makeRequest(`threads/${id}`, "post", opts));
    }

    async deleteThread(id: number, opts?: DeleteThreadOptions): Promise<SuccessResponse> {
        return await assertOk(this.makeRequest(`threads/${id}`, "delete", opts));
    }

    async createThread(forumId: number, title: string, message: string, opts?: CreateThreadOptions): Promise<CreateThreadResponse> {
        return await assertOk(this.makeRequest("threads/", "post", {
            node_id: forumId,
            title: title,
            message: message,
            ...opts
        }));
    }

    async getPost(id: number): Promise<Post> {
        const { post }: {post: Post} = await this.makeRequest(`posts/${id}`, "get");
        return post;
    }

    async getForumThreads(id: number): Promise<GetForumThreadsResponse> {
        return await this.makeRequest(`forums/${id}/threads`, "get");
    }
}

//#region Request types
interface DeleteThreadOptions {
    hard_delete?: boolean;
    reason?: boolean;
    starter_alert?: boolean;
    starter_alert_reason?: boolean;
}

interface GetThreadOptions {
    with_posts?: boolean;
    page?: number;
}

interface CreateThreadOptions {
    prefix_id?: number;
    tags?: string[];
    custom_fields?: Dict<string>;
    discussion_open?: boolean;
    sticky?: boolean;
    attachment_key?: boolean;
}

interface EditThreadOptions {
    prefix_id?: number;
    title?: string;
    discussion_open?: boolean;
    sticky?: boolean;
    custom_fields?: Dict<string>;
    add_tags?: unknown[];
    remove_tags?: unknown[];
}

interface EditPostOptions {
    message?: string;
    silent?: boolean;
    clear_edit?: boolean;
    author_alert?: boolean;
    author_alert_reason?: string;
    attachment_key?: string;
}
//#endregion

//#region Response types 
type GetThreadResponse = {
    thread: Thread;
    messages: Post[];
    pagination: unknown;
};

type SuccessResponse = {
    success: boolean;
}

type EditPostResponse = SuccessResponse & { post: Post };

type CreateThreadResponse = SuccessResponse & { thread: Thread; };

type GetForumThreadsResponse = {
    threads: Thread[];
    pagination: unknown;
    sticky: Thread[];
};
//#endregion

//#region Data types
export interface Forum {
    allow_posting: boolean;
    allow_poll: boolean;
    require_prefix: boolean;
    min_tags: number;
}

export interface User {
    about?: string;
    activity_visible?: boolean;
    age?: number;
    alert_optout?: unknown[];
    allow_post_profile?: string;
    allow_receive_news_feed?: string;
    allow_send_personal_conversation?: string;
    allow_view_identities: string;
    allow_view_profile?: string;
    avatar_urls: unknown;
    can_ban: boolean;
    can_converse: boolean;
    can_edit: boolean;
    can_follow: boolean;
    can_ignore: boolean;
    can_post_profile: boolean;
    can_view_profile: boolean;
    can_view_profile_posts: boolean;
    can_warn: boolean;
    content_show_signature?: boolean;
    creation_watch_state?: string;
    custom_fields?: unknown;
    custom_title?: string;
    dob?: unknown;
    email?: string;
    email_on_conversation?: boolean;
    gravatar?: string;
    interaction_watch_state?: boolean;
    is_admin?: boolean;
    is_banned?: boolean;
    is_discouraged?: boolean;
    is_followed?: boolean;
    is_ignored?: boolean;
    is_moderator?: boolean;
    is_super_admin?: boolean;
    last_activity?: number;
    location: string;
    push_on_conversation?: boolean;
    push_optout?: unknown[];
    receive_admin_email?: boolean;
    secondary_group_ids?: unknown[];
    show_dob_date?: boolean;
    show_dob_year?: boolean;
    signature: string;
    timezone?: string;
    use_tfa?: unknown[];
    user_group_id?: number;
    user_state?: string;
    user_title: string;
    visible?: boolean;
    warning_points?: number;
    website?: string;
    user_id: number;
    username: string;
    message_count: number;
    register_date: number;
    trophy_points: number;
    is_staff: boolean;
    reaction_score: number;
}

export interface Node {
    breadcrumbs: unknown[];
    type_data: unknown;
    node_id: number;
    title: string;
    node_name: string;
    description: string;
    node_type_id: string;
    parent_node_id: number;
    display_order: number;
    display_in_list: boolean;
}

export interface Thread {
    username: string;
    is_watching?: boolean;
    visitor_post_count?: number;
    custom_fields: unknown;
    tags: unknown[];
    prefix?: string;
    can_edit: boolean;
    can_edit_tags: boolean;
    can_reply: boolean;
    can_soft_delete: boolean;
    can_hard_delete: boolean;
    can_view_attachments: boolean;
    Forum?: Node;
    thread_id: number;
    node_id: number;
    title: string;
    reply_count: number;
    view_count: number;
    user_id: number;
    post_date: number;
    sticky: boolean;
    discussion_state: string;
    discussion_open: boolean;
    discussion_type: string;
    first_post_id: number;
    last_post_date: number;
    last_post_id: number;
    last_post_user_id: number;
    last_post_username: string;
    first_post_reaction_score: number;
    prefix_id: number;
}

export interface Attachment {
    filename: string;
    file_size: number;
    height: number;
    width: number;
    thumbnail_url: string;
    video_url: string;
    attachment_id: number;
    content_type: string;
    content_id: number;
    attach_date: number;
    view_count: number;
}

export interface Post {
    username: string;
    is_first_post: boolean;
    is_last_post: boolean;
    can_edit: boolean;
    can_soft_delete: boolean;
    can_hard_delete: boolean;
    can_react: boolean;
    can_view_attachments: boolean;
    Thread?: Thread;
    Attachments?: Attachment[];
    is_reacted_to: boolean;
    visitor_reaction_id: number;
    post_id: number;
    thread_id: number;
    user_id: number;
    post_date: number;
    message: string;
    message_state: string;
    attach_count: number;
    warning_message: string;
    position: number;
    last_edit_date: number;
    reaction_score: number;
    User: User;
}
//#endregion