import path from 'node:path';

import { config } from '@/config';
import InvalidParameterError from '@/errors/types/invalid-parameter';
import { Route } from '@/types';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import ConfigNotFoundError from '@/errors/types/config-not-found';
import { queryToBoolean } from '@/utils/readable-social';

import { baseUrl, getGuild, searchGuildMessages, SearchGuildMessagesParams, HasType, VALID_HAS_TYPES } from './discord-api';

export const route: Route = {
    path: '/search/:guildId/:routeParams',
    categories: ['social-media'],
    example: '/discord/search/302094807046684672/content=friendly&has=image,video',
    parameters: {
        guildId: 'Guild ID',
        routeParams: 'Search parameters, support content, author_id, mentions, has, min_id, max_id, channel_id, pinned',
    },
    features: {
        requireConfig: [
            {
                name: 'DISCORD_AUTHORIZATION',
                description: 'Discord authorization header',
            },
        ],
        requirePuppeteer: false,
        antiCrawler: false,
        supportBT: false,
        supportPodcast: false,
        supportScihub: false,
    },
    name: 'Guild Search',
    maintainers: ['NekoAria'],
    handler,
};

const parseSearchParams = (routeParams?: string): SearchGuildMessagesParams => {
    const parsed = new URLSearchParams(routeParams);
    const hasTypes = parsed.get('has')?.split(',').filter(Boolean);
    const validHasTypes = hasTypes?.filter((type) => VALID_HAS_TYPES.has(type as HasType)) as HasType[];

    const params = {
        content: parsed.get('content') ?? undefined,
        author_id: parsed.get('author_id') ?? undefined,
        mentions: parsed.get('mentions') ?? undefined,
        has: validHasTypes?.length ? validHasTypes : undefined,
        min_id: parsed.get('min_id') ?? undefined,
        max_id: parsed.get('max_id') ?? undefined,
        channel_id: parsed.get('channel_id') ?? undefined,
        pinned: parsed.has('pinned') ? queryToBoolean(parsed.get('pinned')) : undefined,
    };

    return Object.fromEntries(Object.entries(params).filter(([, value]) => value !== undefined));
};

async function handler(ctx) {
    const { authorization } = config.discord || {};
    if (!authorization) {
        throw new ConfigNotFoundError('Discord RSS is disabled due to the lack of authorization config');
    }

    const { guildId } = ctx.req.param();
    const searchParams = parseSearchParams(ctx.req.param('routeParams'));

    if (!Object.keys(searchParams).length) {
        throw new InvalidParameterError('At least one valid search parameter is required');
    }

    const [guildInfo, searchResult] = await Promise.all([getGuild(guildId, authorization), searchGuildMessages(guildId, authorization, searchParams)]);

    if (!searchResult?.messages?.length) {
        return {
            title: `Search Results - ${guildInfo.name}`,
            link: `${baseUrl}/channels/${guildId}`,
            item: [],
            allowEmpty: true,
        };
    }

    const messages = searchResult.messages.flat().map((message) => ({
        title: message.content.split('\n')[0] || '(no content)',
        description: art(path.join(__dirname, 'templates/message.art'), { message, guildInfo }),
        author: message.author.global_name ?? message.author.username,
        pubDate: parseDate(message.timestamp),
        updated: message.edited_timestamp ? parseDate(message.edited_timestamp) : undefined,
        category: [`#${message.channel_id}`],
        link: `${baseUrl}/channels/${guildId}/${message.channel_id}/${message.id}`,
    }));

    const searchDesc = Object.entries(searchParams)
        .filter(([, value]) => value !== undefined)
        .map(([key, value]) => `${key}:${Array.isArray(value) ? value.join(',') : value}`)
        .join(' ');

    return {
        title: `Search "${searchDesc}" in ${guildInfo.name} - Discord`,
        link: `${baseUrl}/channels/${guildId}`,
        item: messages,
        allowEmpty: true,
    };
}
