import { Injectable, Logger } from '@nestjs/common';
import {GoogleTrendsService} from "./google-trends.service";
import {RedditService} from "./reddit.service";
import {HackerNewsService} from "./hackernews.service";
import {RssService} from "./rss.service";
import {NewsApiService} from "./newsapi.service";
import {CollectorResult, TrendItem} from "../../common/interfaces/trend-item.interface";
import {TextUtil} from "../../common/utils/text.util";

export interface OrchestratorResult {
    items: TrendItem[];
    stats: {
        totalRaw: number;
        afterDedup: number;
        bySource: Record<string, number>;
        errors: string[];
        durationMs: number;
    };
}

@Injectable()
export class CollectorOrchestratorService {
    private readonly logger = new Logger(CollectorOrchestratorService.name);

    constructor(
        private readonly googleTrends: GoogleTrendsService,
        private readonly reddit: RedditService,
        private readonly hackerNews: HackerNewsService,
        private readonly rss: RssService,
        private readonly newsApi: NewsApiService,
    ) {}

    /**
     * Thu thập từ TẤT CẢ nguồn, dedup, sort by score
     */
    async collectAll(): Promise<OrchestratorResult> {
        const start = Date.now();
        this.logger.log('🚀 Starting full collection cycle...');

        // Chạy tất cả collectors song song
        const results = await Promise.allSettled([
            // this.googleTrends.collect(),
            this.reddit.collect(['general', 'tech', 'news', 'japan', 'korean']),
            this.hackerNews.collect(),
            this.rss.collect(),
            this.newsApi.collect(),
        ]);

        const collectorResults: CollectorResult[] = [];
        const errors: string[] = [];

        const sourceNames = [
            'google-trends',
            'reddit',
            'hackernews',
            'rss',
            'newsapi',
        ];

        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                collectorResults.push(result.value);
                if (result.value.error) {
                    errors.push(`[${sourceNames[index]}] ${result.value.error}`);
                }
            } else {
                errors.push(
                    `[${sourceNames[index]}] FAILED: ${result.reason?.message}`,
                );
                this.logger.error(
                    `Collector ${sourceNames[index]} crashed: ${result.reason?.message}`,
                );
            }
        });

        // Merge tất cả items
        const allItems = collectorResults.flatMap((r) => r.items);
        const totalRaw = allItems.length;

        // Count by source
        const bySource: Record<string, number> = {};
        for (const item of allItems) {
            const src = item.source.split(':')[0];
            bySource[src] = (bySource[src] || 0) + 1;
        }

        // Dedup
        const deduped = this.deduplicateItems(allItems);

        // Cross-reference bonus: nếu cùng topic xuất hiện ở nhiều source → boost score
        const boosted = this.applyCrossSourceBonus(deduped);

        // Sort by score desc
        boosted.sort((a, b) => b.score - a.score);

        const durationMs = Date.now() - start;

        this.logger.log(
            `✅ Collection complete: ${totalRaw} raw → ${boosted.length} deduped in ${durationMs}ms`,
        );
        this.logger.log(`📊 By source: ${JSON.stringify(bySource)}`);

        if (errors.length > 0) {
            this.logger.warn(`⚠️ Errors: ${errors.length}`);
            errors.forEach((e) => this.logger.warn(`  ${e}`));
        }

        return {
            items: boosted,
            stats: {
                totalRaw,
                afterDedup: boosted.length,
                bySource,
                errors,
                durationMs,
            },
        };
    }

    /**
     * Thu thập từ 1 source cụ thể
     */
    async collectFromSource(source: string): Promise<CollectorResult> {
        switch (source) {
            case 'google-trends':
                return this.googleTrends.collect();
            case 'reddit':
                return this.reddit.collect();
            case 'hackernews':
                return this.hackerNews.collect();
            case 'rss':
                return this.rss.collect();
            case 'newsapi':
                return this.newsApi.collect();
            default:
                throw new Error(`Unknown source: ${source}`);
        }
    }

    /**
     * Dedup dựa trên title similarity (Jaccard >= 0.5)
     */
    private deduplicateItems(items: TrendItem[]): TrendItem[] {
        const result: TrendItem[] = [];
        const fingerprints = new Map<string, number>(); // fingerprint → index in result

        for (const item of items) {
            if (!item.title || item.title.trim().length === 0) continue;

            const fp = TextUtil.fingerprint(item.title);

            // Check exact fingerprint match
            if (fingerprints.has(fp)) {
                const existingIdx = fingerprints.get(fp)!;
                // Giữ item có score cao hơn
                if (item.score > result[existingIdx].score) {
                    result[existingIdx] = item;
                }
                continue;
            }

            // Check similarity với existing items (chỉ check 50 gần nhất để performance)
            let isDuplicate = false;
            const startCheck = Math.max(0, result.length - 50);

            for (let i = startCheck; i < result.length; i++) {
                const similarity = TextUtil.jaccardSimilarity(
                    item.title,
                    result[i].title,
                );
                if (similarity >= 0.5) {
                    // Merge: giữ score cao hơn, gộp source
                    if (item.score > result[i].score) {
                        result[i] = {
                            ...item,
                            tags: [
                                ...new Set([
                                    ...(result[i].tags || []),
                                    ...(item.tags || []),
                                ]),
                            ],
                        };
                    }
                    isDuplicate = true;
                    break;
                }
            }

            if (!isDuplicate) {
                fingerprints.set(fp, result.length);
                result.push(item);
            }
        }

        return result;
    }

    /**
     * Nếu cùng topic xuất hiện trên nhiều source → boost score
     * Vì: multi-source = THỰC SỰ TRENDING
     */
    private applyCrossSourceBonus(items: TrendItem[]): TrendItem[] {
        // Group by similar titles
        const groups: { canonical: TrendItem; sources: Set<string> }[] = [];

        for (const item of items) {
            let foundGroup = false;

            for (const group of groups) {
                const sim = TextUtil.jaccardSimilarity(
                    item.title,
                    group.canonical.title,
                );
                if (sim >= 0.35) {
                    // Looser threshold cho cross-source matching
                    const src = item.source.split(':')[0];
                    group.sources.add(src);
                    foundGroup = true;
                    break;
                }
            }

            if (!foundGroup) {
                const src = item.source.split(':')[0];
                groups.push({ canonical: item, sources: new Set([src]) });
            }
        }

        // Tạo source count map
        const titleBonus = new Map<string, number>();
        for (const group of groups) {
            if (group.sources.size > 1) {
                const bonus = (group.sources.size - 1) * 12; // +12 per extra source
                titleBonus.set(TextUtil.fingerprint(group.canonical.title), bonus);
            }
        }

        // Apply bonus
        return items.map((item) => {
            const fp = TextUtil.fingerprint(item.title);
            const bonus = titleBonus.get(fp) || 0;
            if (bonus > 0) {
                return { ...item, score: Math.min(item.score + bonus, 100) };
            }
            return item;
        });
    }
}
