import type { CheerioAPI } from 'cheerio';
import { load } from 'cheerio';

import type { DataItem } from '@/types';
import cache from '@/utils/cache';
import logger from '@/utils/logger';
import { parseRelativeDate } from '@/utils/parse-date';
import type { Browser, Page } from '@/utils/playwright';

export const BASE_URL = 'https://www.dailypush.dev';

export interface ArticleItem {
    title: string;
    link: string;
    author: DataItem['author'];
    pubDate?: Date;
    category?: string[];
    description?: string;
    articleUrl: string;
    dailyPushUrl?: string;
}

const allowedRequestTypes = new Set(['document']);

async function preparePage(page: Page) {
    await page.setRequestInterception(true);
    page.on('request', (request) => {
        if (allowedRequestTypes.has(request.resourceType())) {
            request.continue();
            return;
        }

        request.abort();
    });
}

export async function fetchPageHtml(browser: Browser, url: string, waitForSelector?: string): Promise<string> {
    const page = await browser.newPage();
    await preparePage(page);

    try {
        logger.http(`Requesting ${url}`);
        await page.goto(url, { waitUntil: 'domcontentloaded' });

        if (waitForSelector) {
            await page.waitForSelector(waitForSelector);
        }

        return await page.content();
    } finally {
        await page.close();
    }
}

/**
 * Try to parse text as a date. Returns the Date if parsing succeeds and is valid, undefined otherwise.
 */
function tryParseAsDate(text: string): Date | undefined {
    try {
        const date = parseRelativeDate(text);
        return Number.isNaN(date.getTime()) ? undefined : date;
    } catch {
        return undefined;
    }
}

/**
 * Extract author from article element
 */
function extractAuthor(article: ReturnType<CheerioAPI>): DataItem['author'] {
    const container = article.find('.flex.items-center.gap-3').first();
    if (container.length === 0) {
        return undefined;
    }

    // Get all content spans (exclude separator spans with "•")
    const allSpans = container.find('span');
    const contentSpans: string[] = [];

    for (let i = 0; i < allSpans.length; i++) {
        const $span = allSpans.eq(i);
        const text = $span.text().trim();
        // Skip separator spans (contain only "•" or have separator classes)
        if (text !== '•' && !$span.hasClass('text-slate-300') && !$span.hasClass('dark:text-slate-600')) {
            contentSpans.push(text);
        }
    }

    // Handle different cases based on number of content spans
    switch (contentSpans.length) {
        case 3:
            // Structure: author, date, reading time
            if (contentSpans[0].includes(',')) {
                const authors: DataItem['author'] = contentSpans[0].split(',').map((author) => ({
                    name: author.trim(),
                }));
                return authors;
            }
            return contentSpans[0];
        case 2: {
            // Two cases:
            // 1. date, reading time (no author)
            // 2. author, date (no reading time)
            const firstText = contentSpans[0];
            if (tryParseAsDate(firstText)) {
                // First is date, so no author
                break;
            }
            // First is author
            return firstText;
        }
        case 1: {
            // Could be date or author
            const text = contentSpans[0];
            if (tryParseAsDate(text)) {
                return undefined;
            }
            return text;
        }
        default:
            break;
    }

    // Fallback: use the post source as author
    const sourceSpan = article.find('span.text-xs.font-medium.uppercase').first();
    if (sourceSpan.length > 0) {
        return sourceSpan.text().trim();
    }

    return undefined;
}

/**
 * Extract categories/tags from article element
 */
function extractCategories(article: ReturnType<CheerioAPI>, $: CheerioAPI): string[] {
    return article
        .find('a[href^="/"]')
        .toArray()
        .map((tagEl) => {
            const tagElement = $(tagEl);
            const tagHref = tagElement.attr('href');
            const tagText = tagElement.text().trim();

            // Skip summary/stats links and navigation
            if (tagHref && tagText && !tagHref.includes('article/') && !tagHref.includes('Summary') && tagText.length < 50 && !/^(Summary|stats|About|Tags|Toggle|Trending|Latest|Previous|Next)$/i.test(tagText)) {
                return tagText;
            }
            return null;
        })
        .filter((tagText): tagText is string => tagText !== null);
}

/**
 * Extract publication date from article element
 */
function extractPubDate(article: ReturnType<CheerioAPI>): Date | undefined {
    const container = article.find('.flex.items-center.gap-3').first();
    if (container.length === 0) {
        return undefined;
    }

    // Get all content spans (exclude separator spans with "•")
    const allSpans = container.find('span');
    const contentSpans: string[] = [];

    for (let i = 0; i < allSpans.length; i++) {
        const $span = allSpans.eq(i);
        const text = $span.text().trim();
        // Skip separator spans (contain only "•" or have separator classes)
        if (text !== '•' && !$span.hasClass('text-slate-300') && !$span.hasClass('dark:text-slate-600')) {
            contentSpans.push(text);
        }
    }

    let dateText: string | undefined;

    // Handle different cases based on number of content spans
    switch (contentSpans.length) {
        case 3:
            // Structure: author, date, reading time
            dateText = contentSpans[1];
            break;
        case 2: {
            // Two cases:
            // 1. date, reading time (no author)
            // 2. author, date (no reading time)
            const firstText = contentSpans[0];
            dateText = tryParseAsDate(firstText) ? firstText : contentSpans[1];
            break;
        }
        case 1: {
            // Could be date or author
            const text = contentSpans[0];
            if (tryParseAsDate(text)) {
                dateText = text;
            }
            break;
        }
        default:
            break;
    }

    return dateText ? tryParseAsDate(dateText) : undefined;
}

/**
 * Parse a single article element into an ArticleItem
 */
function parseArticle(article: ReturnType<CheerioAPI>, $: CheerioAPI, baseUrl: string): (DataItem & ArticleItem) | null {
    // Find the title link in h2 > a
    const titleLink = article.find('h2 a[href^="http"]');
    if (titleLink.length === 0) {
        return null;
    }

    const title = titleLink.text().trim();
    const link = titleLink.attr('href');

    if (!title || !link || link.includes('dailypush.dev')) {
        return null;
    }

    const author = extractAuthor(article);
    const description = article.find('p.text-sm.text-muted-foreground').text().trim();
    const categories = extractCategories(article, $);

    const footer = article.find('.flex.items-center.justify-between.gap-4.flex-wrap');
    const summaryLink = footer.find('a[href*="/article/"]').attr('href');
    const dailyPushUrl = summaryLink ? `${baseUrl}${summaryLink}` : undefined;

    const pubDate = extractPubDate(article);

    return {
        title,
        link,
        author,
        pubDate,
        category: categories.length > 0 ? categories : undefined,
        description,
        articleUrl: link,
        dailyPushUrl,
        language: 'en',
    };
}

/**
 * Parse all articles from the page
 */
export function parseArticles($: CheerioAPI, baseUrl: string): ArticleItem[] {
    return $('article')
        .toArray()
        .map((articleEl) => {
            const article = $(articleEl);
            return parseArticle(article, $, baseUrl);
        })
        .filter((parsed): parsed is ArticleItem => parsed !== null);
}

/**
 * Enhance items with full summaries from dailypush article pages.
 * Uses the provided browser; opens a new tab per URL (document requests only). Caller must close the browser.
 */
export async function enhanceItemsWithSummaries(browser: Browser, items: ArticleItem[]): Promise<DataItem[]> {
    const itemsWithUrl = items.filter((item) => item.dailyPushUrl !== undefined);
    const itemsWithoutUrl: DataItem[] = items.filter((item) => item.dailyPushUrl === undefined);

    const enhancedItems = await Promise.all(
        itemsWithUrl.map((item) =>
            cache.tryGet(item.dailyPushUrl!, async () => {
                try {
                    const html = await fetchPageHtml(browser, item.dailyPushUrl!, 'p.font-ibm-plex-sans.leading-relaxed');
                    const $ = load(html);
                    const summary = $('p.font-ibm-plex-sans.leading-relaxed');
                    if (summary.length > 0 && summary.text().trim()) {
                        item.description = summary.text().trim();
                    }
                } catch {
                    // If fetching article page fails, keep the original description
                }

                return item;
            })
        )
    );

    return [...enhancedItems, ...itemsWithoutUrl];
}
