/**
 * 基础导出器
 * 定义导出器的通用接口和基础功能
 * 所有具体格式的导出器都应继承此基类
 */

import fs from 'fs';
import path from 'path';
import { RawMessage } from 'NapCatQQ/src/core/index.js';
import { 
    ExportFormat, 
    ExportResult, 
    SystemError, 
    ErrorType 
} from '../../types/index.js';
import { MessageParser, MessageParserConfig, SenderTitleResolver } from '../parser/MessageParser.js';
import { SimpleMessageParser } from '../parser/SimpleMessageParser.js';
import { NapCatCore } from 'NapCatQQ/src/core/index.js';

/**
 * 导出选项接口
 */
export interface ExportOptions {
    /** 输出文件路径 */
    outputPath: string;
    /** 是否包含资源链接 */
    includeResourceLinks: boolean;
    /** 是否包含系统消息 */
    includeSystemMessages: boolean;
    /** 是否过滤掉纯图片消息 */
    filterPureImageMessages: boolean;
    /** 时间格式 */
    timeFormat: string;
    /** 是否美化输出（适用于JSON等格式） */
    prettyFormat: boolean;
    /** 自定义CSS样式（适用于HTML格式） */
    customCss?: string;
    /** 编码格式 */
    encoding: string;
    /** 分块大小（大文件分块输出） */
    chunkSize?: number;
    /** 群聊导出时是否优先使用群成员名称 */
    preferGroupMemberName?: boolean;
    /**
     * 可选：发件人群头衔解析器。调用侧需在导出前预拉取群成员信息。
     * 仅在群聊（chatType=2）上被调用。返回空字符串或 undefined 代表“无头衔”。
     */
    senderTitleResolver?: SenderTitleResolver;
    /**
     * issue #277：消息资源映射 (msgId → resources[])，由 ApiServer 在导出前
     * 通过 ResourceHandler.processMessageResources 预先下载资源后构造。
     * JSON / JSONL / Excel / TXT 等导出器据此把每条消息引用的资源路径写到
     * 输出里，并在导出完成后把资源文件复制到 `<outputDir>/resources/<type>/`。
     * 不传或传空 Map 时回退到原有行为（不写资源路径，不拷贝资源）。
     */
    resourceMap?: Map<string, any[]>;
}

/**
 * 导出进度回调函数类型
 */
export type ProgressCallback = (progress: {
    current: number;
    total: number;
    percentage: number;
    message: string;
}) => void;

/**
 * 基础导出器抽象类
 * 提供导出功能的通用框架和工具方法
 */
export abstract class BaseExporter {
    protected readonly format: ExportFormat;
    protected readonly options: ExportOptions;
    protected readonly core?: NapCatCore; // 添加NapCatCore实例
    protected cancelled: boolean;
    protected progressCallback: ProgressCallback | null;

    /**
     * 构造函数
     * @param format 导出格式
     * @param options 导出选项
     * @param core NapCatCore实例（可选）
     */
    constructor(format: ExportFormat, options: ExportOptions, core?: NapCatCore) {
        this.format = format;
        this.core = core;
        this.options = {
            outputPath: options.outputPath,
            includeResourceLinks: options.includeResourceLinks ?? true,
            includeSystemMessages: options.includeSystemMessages ?? true,
            filterPureImageMessages: options.filterPureImageMessages ?? false,
            timeFormat: options.timeFormat || 'YYYY-MM-DD HH:mm:ss',
            prettyFormat: options.prettyFormat ?? true,
            encoding: options.encoding || 'utf-8',
            customCss: options.customCss,
            chunkSize: options.chunkSize,
            preferGroupMemberName: options.preferGroupMemberName ?? true,
            senderTitleResolver: options.senderTitleResolver,
            resourceMap: options.resourceMap
        };
        
        this.cancelled = false;
        this.progressCallback = null;
    }

    /**
     * 设置进度回调
     */
    setProgressCallback(callback: ProgressCallback | null): void {
        this.progressCallback = callback;
    }

    /**
     * 取消导出
     */
    cancel(): void {
        this.cancelled = true;
    }

    /**
     * 导出公共方法
     * @param messages 原始消息数组
     * @param chatInfo 聊天信息
     * @returns 导出结果
     */
    async export(messages: RawMessage[], chatInfo?: any): Promise<ExportResult> {
        const startTime = Date.now();
        
        try {
            this.updateProgress(0, messages.length, `开始${this.format}导出`);
            
            this.ensureOutputDirectory();
            
            // 过滤掉空消息
            const validMessages = messages.filter(m => m);
            
            // 按时间戳排序消息，确保时间顺序正确
            const sortedMessages = this.sortMessagesByTimestamp(validMessages);
            console.log(`[${this.format}Exporter] 消息排序完成: ${validMessages.length} → ${sortedMessages.length} 条`);
            
            // 应用纯图片消息过滤
            const filteredMessages = await this.applyPureImageFilter(sortedMessages);
            console.log(`[${this.format}Exporter] 消息过滤完成: ${sortedMessages.length} → ${filteredMessages.length} 条`);
            
            const content = await this.generateContent(filteredMessages, chatInfo);
            
            if (this.cancelled) {
                throw new Error('导出已取消');
            }
            
            await this.writeToFile(content);
            
            this.updateProgress(filteredMessages.length, filteredMessages.length, '导出完成');
            
            const resourceCount = filteredMessages.reduce((acc, msg) => {
                const elements = msg.elements || [];
                return acc + elements.filter(e => e.picElement || e.fileElement).length;
            }, 0);

            return {
                taskId: '',
                format: this.format,
                filePath: this.options.outputPath,
                fileSize: this.getFileSize(),
                messageCount: filteredMessages.length,
                resourceCount: resourceCount,
                exportTime: Date.now() - startTime,
                completedAt: new Date()
            };
            
        } catch (error) {
            throw this.wrapError(error, `${this.format}Export`);
        }
    }

    /**
     * 生成内容的抽象方法
     * 各子类必须实现此方法
     */
    protected abstract generateContent(messages: RawMessage[], chatInfo?: any): Promise<string>;

    /**
     * 检查输出目录是否存在，不存在则创建
     */
    protected ensureOutputDirectory(): void {
        const dir = path.dirname(this.options.outputPath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
        }
    }

    /**
     * issue #277：把 resourceMap 中已下载的资源文件复制到导出目录的
     * `resources/<typeDir>/<fileName>` 下，与 HTML 导出器保持目录布局一致，
     * 这样 JSON / JSONL / TXT / Excel 输出引用的相对路径都能落地到真实文件。
     *
     * - 重复文件按目标路径存在性跳过；
     * - 单个资源拷贝失败仅 logWarn，不中断导出；
     * - resourceMap 为空 / 未配置时直接 no-op。
     */
    protected async copyResourcesAlongsideExport(outputDir: string): Promise<number> {
        const map = this.options.resourceMap;
        if (!map || map.size === 0) return 0;

        const stream = await import('stream/promises');
        const typeOf = (t: string) => {
            switch (t) {
                case 'image': return 'images';
                case 'video': return 'videos';
                case 'audio': return 'audios';
                case 'file':  return 'files';
                default:      return 'files';
            }
        };

        let copied = 0;
        const seen = new Set<string>();
        for (const resources of map.values()) {
            if (!Array.isArray(resources)) continue;
            for (const r of resources) {
                if (!r || typeof r !== 'object') continue;
                const localPath: string | undefined = r.localPath;
                if (!localPath || typeof localPath !== 'string' || localPath.trim() === '') continue;
                if (!fs.existsSync(localPath)) continue;
                try {
                    const stat = fs.statSync(localPath);
                    if (!stat.isFile()) continue;
                } catch {
                    continue;
                }

                const fileName = path.basename(localPath);
                const typeDir = typeOf(String(r.type || 'file'));
                const targetDir = path.join(outputDir, 'resources', typeDir);
                const targetPath = path.join(targetDir, fileName);
                if (seen.has(targetPath)) continue;
                seen.add(targetPath);

                if (fs.existsSync(targetPath)) {
                    copied++;
                    continue;
                }
                try {
                    fs.mkdirSync(targetDir, { recursive: true });
                    await stream.pipeline(
                        fs.createReadStream(localPath),
                        fs.createWriteStream(targetPath)
                    );
                    copied++;
                } catch (err) {
                    console.warn(`[${this.format}Exporter] 拷贝资源失败 ${localPath} → ${targetPath}:`, err);
                }
            }
        }
        if (copied > 0) {
            console.log(`[${this.format}Exporter] 已复制 ${copied} 个资源文件到 ${path.join(outputDir, 'resources')}`);
        }
        return copied;
    }

    /**
     * 获取消息解析器实例
     */
    protected getMessageParser(core: NapCatCore): MessageParser {
        const config: MessageParserConfig = {
            includeResourceLinks: this.options.includeResourceLinks,
            includeSystemMessages: this.options.includeSystemMessages,
            parseMarketFace: true,
            parseCardMessages: true,
            parseMultiForward: true,
            fetchUserInfo: false,
            timeFormat: this.options.timeFormat,
            maxTextLength: 50000,
            debugMode: false,
            rawStrategy: 'none',  // 不生成raw字段，大幅减少JSON文件大小（从~140行/条缩减到~20行/条）
            html: 'none',         // JSON导出不需要HTML
            obParseTimeoutMs: 400,
            quickReply: true,
            obMode: 'prefer-native',
            fallback: 'native',
            progressEvery: 100,
            yieldEvery: 1000,
            suppressFallbackWarn: true,
            stopOnAbort: true,
            preferGroupMemberName: this.options.preferGroupMemberName ?? true,
            senderTitleResolver: this.options.senderTitleResolver
        };
        
        return new MessageParser(core, config);
    }

    /**
     * 写入文件
     */
    protected async writeToFile(content: string): Promise<void> {
        try {
            this.ensureOutputDirectory();
            await fs.promises.writeFile(this.options.outputPath, content, { encoding: this.options.encoding as BufferEncoding });
        } catch (error) {
            throw this.wrapError(error, 'writeToFile');
        }
    }

    /**
     * 获取文件大小
     */
    protected getFileSize(): number {
        try {
            const stats = fs.statSync(this.options.outputPath);
            return stats.size;
        } catch (error) {
            return 0;
        }
    }

    /**
     * 更新进度
     */
    protected updateProgress(current: number, total: number, message: string): void {
        if (this.progressCallback) {
            const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
            this.progressCallback({
                current,
                total,
                percentage,
                message
            });
        }
    }

    /**
     * HTML转义
     */
    protected escapeHtml(text: string): string {
        const htmlEscapes: Record<string, string> = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#39;'
        };
        
        return text.replace(/[&<>"']/g, (match) => htmlEscapes[match] || match);
    }

    /**
     * 格式化时间戳
     */
    protected formatTimestamp(timestamp: Date): string {
        const year = timestamp.getFullYear();
        const month = String(timestamp.getMonth() + 1).padStart(2, '0');
        const day = String(timestamp.getDate()).padStart(2, '0');
        const hours = String(timestamp.getHours()).padStart(2, '0');
        const minutes = String(timestamp.getMinutes()).padStart(2, '0');
        const seconds = String(timestamp.getSeconds()).padStart(2, '0');
        
        switch (this.options.timeFormat) {
            case 'date-only':
                return `${year}-${month}-${day}`;
            case 'time-only':
                return `${hours}:${minutes}:${seconds}`;
            case 'relative':
                return this.getRelativeTime(timestamp);
            default:
                return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
        }
    }

    /**
     * 获取相对时间
     */
    private getRelativeTime(timestamp: Date): string {
        const now = new Date();
        const diff = now.getTime() - timestamp.getTime();
        const minutes = Math.floor(diff / 60000);
        const hours = Math.floor(minutes / 60);
        const days = Math.floor(hours / 24);

        if (days > 0) {
            return `${days}天前`;
        } else if (hours > 0) {
            return `${hours}小时前`;
        } else if (minutes > 0) {
            return `${minutes}分钟前`;
        } else {
            return '刚刚';
        }
    }

    /**
     * 包装错误
     */
    protected wrapError(error: any, operation: string): SystemError {
        return new SystemError({
            type: ErrorType.API_ERROR,
            message: `${operation} 操作失败: ${error.message || error}`,
            details: error,
            timestamp: new Date(),
            context: { operation, options: this.options }
        });
    }

    /**
     * 纯图片消息过滤（已废弃）
     */
    protected async applyPureImageFilter(messages: RawMessage[]): Promise<RawMessage[]> {
        return messages;
    }

    /**
     * 按时间戳排序消息
     * 确保消息按发送时间从早到晚的顺序排列
     * 
     * @param messages 原始消息数组
     * @returns 按时间排序后的消息数组
     */
    protected sortMessagesByTimestamp(messages: RawMessage[]): RawMessage[] {
        const sortedMessages = [...messages].sort((a, b) => {
            // 解析时间戳
            let timeA = parseInt(a.msgTime || '0');
            let timeB = parseInt(b.msgTime || '0');
            
            // 处理无效时间戳
            if (isNaN(timeA) || timeA <= 0) {
                console.warn(`[BaseExporter] 消息 ${a.msgId} 的时间戳无效: ${a.msgTime}`);
                timeA = 0; // 无效时间戳放到最前面
            }
            if (isNaN(timeB) || timeB <= 0) {
                console.warn(`[BaseExporter] 消息 ${b.msgId} 的时间戳无效: ${b.msgTime}`);
                timeB = 0;
            }
            
            // 检查是否为秒级时间戳并转换为毫秒级进行比较
            // 但保持原始数据不变
            let compareTimeA = timeA;
            let compareTimeB = timeB;
            
            // 如果是秒级时间戳（10位数），转换为毫秒级用于比较
            if (timeA > 1000000000 && timeA < 10000000000) {
                compareTimeA = timeA * 1000;
            }
            if (timeB > 1000000000 && timeB < 10000000000) {
                compareTimeB = timeB * 1000;
            }
            
            // 按时间从早到晚排序
            return compareTimeA - compareTimeB;
        });
        
        // 输出排序统计信息
        if (sortedMessages.length > 0) {
            const firstTime = sortedMessages[0]?.msgTime;
            const lastTime = sortedMessages[sortedMessages.length - 1]?.msgTime;
            console.log(`[BaseExporter] 消息排序: 时间范围从 ${firstTime} 到 ${lastTime}`);
        }
        
        return sortedMessages;
    }

    // ====== [新增] 受保护工具：创建写流 ======
    protected createWriteStream(filePath: string): import('fs').WriteStream {
        this.ensureOutputDirectory();
        return fs.createWriteStream(filePath, { encoding: this.options.encoding as BufferEncoding });
    }

    // ====== [新增] 受保护工具：内存监控与可选 GC ======
    protected monitorMemory(hint: string, gc = false): void {
        try {
            const mu = process.memoryUsage?.();
            const heapMB = mu?.heapUsed ? Math.round(mu.heapUsed / 1e6) : 0;
            console.log(`[${this.format}Exporter] [Mem] ${hint} heapUsed≈${heapMB}MB`);
            if (gc && typeof global.gc === 'function') {
                const before = heapMB;
                global.gc();
                const after = Math.round(process.memoryUsage().heapUsed / 1e6);
                console.log(`[${this.format}Exporter] [GC] ${hint} ${before}MB → ${after}MB`);
            }
        } catch {}
    }
}
