import { createHash } from "crypto";
import { mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } from "fs/promises";
import { Context, h, Logger, Session } from "koishi";
import { AssetData, PromptService } from "koishi-plugin-yesimbot/services";
import { Services } from "koishi-plugin-yesimbot/shared";
import path from "path";
import { pathToFileURL } from "url";
import { StickerConfig } from "./index";

// 添加表情包表结构
interface StickerRecord {
    id: string;
    category: string;
    filePath: string;
    source: {
        platform: string;
        channelId: string;
        userId: string;
        messageId: string;
    };
    createdAt: Date;
}

const TableName = "yesimbot.stickers";

declare module "koishi" {
    interface Tables {
        [TableName]: StickerRecord;
    }
}

export class StickerService {
    public logger: Logger;

    private static tablesRegistered = false;
    public isReady: boolean = false;

    constructor(
        private ctx: Context,
        private config: StickerConfig
    ) {
        this.logger = ctx[Services.Logger].getLogger("[表情管理]");
        this.start();
    }

    private async start() {
        // 确保初始化只执行一次
        if (this.isReady) return;

        await this.initStorage();
        await this.registerModels();
        this.registerPromptSnippet();

        // 标记服务已就绪
        this.isReady = true;
        this.logger.debug("表情包服务已就绪");
    }

    public whenReady() {
        return new Promise<void>((resolve) => {
            if (this.isReady) {
                resolve();
            } else {
                const check = () => {
                    if (this.isReady) {
                        resolve();
                    } else {
                        setTimeout(check, 100);
                    }
                };
                check();
            }
        });
    }

    private registerPromptSnippet() {
        const promptService: PromptService = this.ctx[Services.Prompt];
        if (!promptService) {
            this.logger.warn("提示词服务未找到，无法注册分类列表");
            return;
        }

        // 注册动态片段
        promptService.registerSnippet("sticker.categories", async () => {
            const categories = await this.getCategories();
            return categories.join(", ");
        });

        this.logger.debug("表情包分类列表已注册到提示词系统");
    }

    private async initStorage() {
        await mkdir(this.config.storagePath, { recursive: true });
        this.logger.info(`表情存储目录已初始化: ${this.config.storagePath}`);
    }

    private async registerModels() {
        // 确保表只注册一次
        if (StickerService.tablesRegistered) return;
        StickerService.tablesRegistered = true;

        try {
            // 使用 extend 创建表
            this.ctx.model.extend(
                TableName,
                {
                    id: "string(64)",
                    category: "string(255)",
                    filePath: "string(255)",
                    source: "json",
                    createdAt: "timestamp",
                },
                { primary: "id" }
            );

            this.logger.debug("表情包表已创建");
        } catch (error) {
            this.logger.error("创建表情包表失败", error);
            throw error;
        }
    }

    /**
     * 偷取表情包
     * @param image_id string
     * @param session
     * @returns
     */
    public async stealSticker(image_id: string, session: Session): Promise<StickerRecord> {
        const assetService = this.ctx[Services.Asset];

        const imageDataForLLM = (await assetService.read(image_id, {
            format: "data-url",
            image: { process: true, format: "jpeg" },
        })) as string;
        const imageData = (await assetService.read(image_id, { format: "buffer" })) as Buffer;

        // 生成唯一ID - 使用URL作为哈希输入
        const hash = createHash("sha256");
        hash.update(image_id);
        const stickerId = hash.digest("hex");

        // 目标文件路径
        // 从b64获取mime
        const mimeType = imageDataForLLM.split(";")[0].split(":")[1];
        const extension = this.getExtensionFromContentType(mimeType) || "png";
        const destPath = path.resolve(this.config.storagePath, `${stickerId}.${extension}`);

        // 保存文件到表情目录
        await writeFile(destPath, imageData);

        // 分类表情
        const category = await this.classifySticker(imageDataForLLM);

        // 创建数据库记录
        const record: StickerRecord = {
            id: stickerId,
            category,
            filePath: destPath,
            source: {
                platform: session.platform,
                channelId: session.channelId,
                userId: session.userId,
                messageId: session.messageId,
            },
            createdAt: new Date(),
        };

        await this.ctx.database.create(TableName, record);
        this.logger.debug(`已保存表情: ${category} - ${stickerId}`);
        return record;
    }

    private async classifySticker(imageData: string): Promise<string> {
        // 动态获取分类列表
        const categories = await this.getCategories();
        const categoryList = categories.join(", ");

        // 使用分类列表替换模板中的占位符
        const prompt = this.config.classificationPrompt.replace("{{categories}}", categoryList);

        const model = this.ctx[Services.Model].getChatModel(this.config.classifiModel.providerName, this.config.classifiModel.modelId);

        if (!model || !model.isVisionModel()) {
            this.logger.error(`当前模型组中没有支持多模态的模型。`);
            throw Error();
        }

        try {
            const response = await model.chat({
                messages: [
                    {
                        role: "user",
                        content: [
                            { type: "text", text: prompt }, // 使用动态生成的提示词
                            {
                                type: "image_url",
                                image_url: {
                                    url: imageData,
                                },
                            },
                        ],
                    },
                ],
            });

            return response.text.trim();
        } catch (error) {
            this.logger.error("表情分类失败", error);
            return "分类失败";
        }
    }

    /**
     * 从外部文件夹导入表情包
     * @param sourceDir 源文件夹路径
     * @param session 会话对象（用于日志记录）
     * @returns 导入结果统计信息
     */
    public async importFromDirectory(sourceDir: string, session: Session): Promise<ImportStats> {
        // 初始化统计数据
        const stats: ImportStats = {
            total: 0,
            success: 0,
            failed: 0,
            skipped: 0,
            failedFiles: [],
        };

        // 检查源目录是否存在
        if (!(await this.dirExists(sourceDir))) {
            throw new Error(`源目录不存在: ${sourceDir}`);
        }

        // 创建进度消息
        const progressMsg = await session.sendQueued("开始导入表情包，正在扫描目录...");

        try {
            // 获取所有子目录（每个目录作为一个分类）
            const subdirs = await this.getValidSubdirectories(sourceDir);

            for (const [index, subdir] of subdirs.entries()) {
                // 更新进度

                const category = path.basename(subdir);
                const files = await this.getImageFiles(subdir);
                stats.total += files.length;

                // 导入当前分类下的所有图片
                for (const file of files) {
                    try {
                        const filePath = path.join(subdir, file);
                        const result = await this.importSingleSticker(filePath, category);

                        if (result === "success") {
                            stats.success++;
                        } else {
                            stats.skipped++;
                        }
                    } catch (error) {
                        stats.failed++;
                        stats.failedFiles.push(file);
                        this.logger.warn(`导入失败: ${file} - ${error.message}`);
                    }
                }
            }
        } finally {
            // 移除进度消息
        }

        return stats;
    }

    /** 获取有效的子目录列表 */
    private async getValidSubdirectories(dir: string): Promise<string[]> {
        const items = await readdir(dir, { withFileTypes: true });
        return items.filter((item) => item.isDirectory()).map((item) => path.join(dir, item.name));
    }

    /** 获取目录下的所有图片文件 */
    private async getImageFiles(dir: string): Promise<string[]> {
        const items = await readdir(dir, { withFileTypes: true });
        return items.filter((item) => item.isFile() && this.isValidImageType(item.name)).map((item) => item.name);
    }

    /** 校验文件类型 */
    private isValidImageType(fileName: string): boolean {
        const ext = path.extname(fileName).toLowerCase().slice(1);
        return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext);
    }

    /** 计算文件哈希值 */
    private async calculateFileHash(filePath: string): Promise<string> {
        const buffer = await readFile(filePath);
        const hash = createHash("sha256");
        hash.update(buffer);
        return hash.digest("hex");
    }

    private async saveImageToLocal(url: string, content: ArrayBuffer, contentType: string): Promise<{ localPath: string }> {
        const id = createHash("sha256").update(url).digest("hex");
        const extension = contentType.split("/")[1] || "bin";
        const fileName = `${id}.${extension}`;
        const filePath = path.join(this.config.storagePath, fileName);

        await writeFile(filePath, Buffer.from(content));
        return { localPath: filePath };
    }

    /**
     * 规范化 emojihub-bili URL
     * 处理特定格式的部分 URL
     */
    private normalizeEmojiHubUrl(rawUrl: string): string {
        // 1. 完整的 URL 直接返回
        if (rawUrl.startsWith("http://") || rawUrl.startsWith("https://")) {
            return rawUrl;
        }

        // 2. 处理特定前缀问题 (如重复的 "https:")
        if (rawUrl.startsWith("https:https://")) {
            return rawUrl.replace("https:", "");
        }

        // 3. 添加 B 站默认前缀
        if (rawUrl.startsWith("bfs/") || rawUrl.startsWith("/bfs/")) {
            return `https://i0.hdslb.com/${rawUrl.replace(/^\//, "")}`;
        }

        // 4. 添加 Koishi Meme 默认前缀
        if (rawUrl.startsWith("meme/") || rawUrl.startsWith("/meme/")) {
            return `https://memes.none.bot/${rawUrl.replace(/^\//, "")}`;
        }

        // 5. 其他情况视为相对路径
        return `https://i0.hdslb.com/bfs/${rawUrl}`;
    }

    /** 检查目录是否存在 */
    private async dirExists(dir: string): Promise<boolean> {
        try {
            await readdir(dir);
            return true;
        } catch {
            return false;
        }
    }

    async getCategories(): Promise<string[]> {
        const records = await this.ctx.database.select(TableName).execute();

        return [...new Set(records.map((r) => r.category))];
    }

    async getRandomSticker(category: string): Promise<h> {
        const records = await this.ctx.database.select(TableName).where({ category }).execute();

        if (records.length === 0) return null;

        const randomIndex = Math.floor(Math.random() * records.length);
        const sticker = records[randomIndex];

        const fileUrl = pathToFileURL(sticker.filePath).href;

        const ext = sticker.filePath.split(".").pop();

        const b64 = await readFile(sticker.filePath, "base64");
        const base64Data = `data:image/${ext};base64,${b64}`;

        return h.image(base64Data, { "sub-type": "1" });
    }

    async getStickersByCategory(category: string): Promise<StickerRecord[]> {
        const records = await this.ctx.database.select(TableName).where({ category }).execute();

        if (records.length === 0) return [];

        return records;
    }

    public async importEmojiHubTxt(filePath: string, category: string, session: Session): Promise<ImportStats> {
        const stats: ImportStats = {
            total: 0,
            success: 0,
            failed: 0,
            skipped: 0,
            failedUrls: [],
        };

        // 读取 TXT 文件
        let urls: string[];
        try {
            const content = await readFile(filePath, "utf-8");
            urls = content
                .split("\n")
                .map((url) => url.trim())
                .filter((url) => url.length > 0);
        } catch (error) {
            throw new Error(`无法读取文件: ${error.message}`);
        }

        stats.total = urls.length;
        if (stats.total === 0) {
            throw new Error("文件为空或没有有效的 URL");
        }

        // 创建进度消息
        const progressMsg = await session.sendQueued(`开始导入表情包，共 ${stats.total} 个 URL...`);

        try {
            // 准备临时下载目录
            const tempDir = path.join(this.config.storagePath, "temp");
            await mkdir(tempDir, { recursive: true });
            this.logger.debug(`创建临时目录: ${tempDir}`);

            // 处理每个 URL
            for (const [index, rawUrl] of urls.entries()) {
                // 更新进度消息
                if (index % 100 === 0 && progressMsg) {
                    await session.sendQueued(`已处理 ${index}/${urls.length} 个 URL...`);
                }

                try {
                    // 规范化 URL
                    const url = this.normalizeEmojiHubUrl(rawUrl);

                    // 使用 fetch API 下载图片
                    const response = await this.fetchWithTimeout(url, 15000);

                    if (!response.ok) {
                        throw new Error(`HTTP ${response.status} ${response.statusText}`);
                    }

                    // 获取内容类型
                    const contentType = response.headers.get("content-type") || "image/jpeg";

                    // 获取文件扩展名
                    const extension = this.getExtensionFromContentType(contentType) || "bin";

                    // 生成文件名 (使用URL哈希)
                    const fileHash = createHash("sha256").update(url).digest("hex");
                    const tempFilePath = path.join(this.config.storagePath, `${fileHash}.${extension}`);

                    // 将图片数据写入文件
                    const buffer = await response.arrayBuffer();
                    await writeFile(tempFilePath, Buffer.from(buffer));
                    this.logger.debug(`已下载图片: ${tempFilePath}`);

                    // 使用 importSingleSticker 方法导入
                    const result = await this.importSingleSticker(tempFilePath, category, session);

                    if (result === "success") {
                        stats.success++;
                    } else if (result === "duplicate") {
                        stats.skipped++;

                        // 清理重复文件
                        try {
                            await unlink(tempFilePath);
                        } catch (cleanupError) {
                            this.logger.warn(`清理临时文件失败: ${tempFilePath}`, cleanupError);
                        }
                    }
                } catch (error) {
                    stats.failed++;
                    stats.failedUrls.push({ url: rawUrl, error: error.message });
                    this.logger.warn(`导入失败: ${rawUrl} - ${error.message}`);
                }
            }
        } finally {
            // 移除进度消息
            if (progressMsg) {
                // await session.cancelQueued(progressMsg);
            }

            // await this.cleanupTempDir(tempDir);
        }

        return stats;
    }

    /**
     * 根据Content-Type获取文件扩展名
     */
    private getExtensionFromContentType(contentType: string): string | null {
        const mimeMap: Record<string, string> = {
            "image/jpeg": "jpg",
            "image/jpg": "jpg",
            "image/png": "png",
            "image/gif": "gif",
            "image/webp": "webp",
            "image/svg+xml": "svg",
            "image/bmp": "bmp",
        };

        // 移除参数部分（如 charset）
        const cleanType = contentType.split(";")[0].trim().toLowerCase();
        return mimeMap[cleanType] || null;
    }

    /**
     * 自定义 fetch 方法，带超时控制
     */
    private async fetchWithTimeout(url: string, timeout: number): Promise<Response> {
        return new Promise((resolve, reject) => {
            // 设置超时定时器
            const timeoutId = setTimeout(() => {
                reject(new Error("请求超时"));
            }, timeout);

            // 发起 fetch 请求
            fetch(url)
                .then((response) => {
                    clearTimeout(timeoutId);
                    resolve(response);
                })
                .catch((error) => {
                    clearTimeout(timeoutId);
                    reject(error);
                });
        });
    }
    /**
     * 清理临时目录
     */
    private async cleanupTempDir(tempDir: string) {
        try {
            const files = await readdir(tempDir);
            for (const file of files) {
                const filePath = path.join(tempDir, file);
                await unlink(filePath);
            }
            await rmdir(tempDir);
            this.logger.debug(`已清理临时目录: ${tempDir}`);
        } catch (error) {
            this.logger.warn(`清理临时目录失败: ${error.message}`);
        }
    }

    /**
     * 增强版 importSingleSticker 方法
     */
    private async importSingleSticker(filePath: string, category: string, session?: Session): Promise<"success" | "duplicate"> {
        // 校验文件类型
        if (!this.isValidImageFile(filePath)) {
            throw new Error("不支持的文件类型");
        }

        // 检查文件是否已存在
        const fileHash = await this.calculateFileHash(filePath);
        const existing = await this.ctx.database.get(TableName, { id: fileHash });
        if (existing.length > 0) {
            return "duplicate";
        }

        // 获取文件扩展名
        const extension = path.extname(filePath) || ".png";

        // 目标文件路径
        const destPath = path.resolve(this.config.storagePath, `${fileHash}${extension}`);

        // 移动文件到表情包目录
        await rename(filePath, destPath);

        // 创建数据库记录
        const record: StickerRecord = {
            id: fileHash,
            category,
            filePath: destPath,
            source: {
                platform: session?.platform || "import",
                channelId: session?.channelId || "",
                userId: session?.userId || "",
                messageId: session?.messageId || "",
            },
            createdAt: new Date(),
        };

        await this.ctx.database.create(TableName, record);
        this.logger.info(`已导入表情: ${category}/${fileHash}${extension}`);

        return "success";
    }

    /**
     * 增强版文件类型验证
     */
    private isValidImageFile(filePath: string): boolean {
        try {
            const extension = path.extname(filePath).toLowerCase().slice(1);
            return ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(extension);
        } catch {
            return false;
        }
    }

    public async renameCategory(oldName: string, newName: string): Promise<number> {
        const result = await this.ctx.database.set(TableName, { category: oldName }, { category: newName });
        const modified = result.matched;
        this.logger.info(`已将分类 "${oldName}" 重命名为 "${newName}"，更新了 ${modified} 个表情包`);
        return modified;
    }

    public async deleteCategory(category: string): Promise<number> {
        // 获取该分类的所有表情包
        const stickers = await this.ctx.database.get(TableName, {
            category: { $eq: category },
        });

        // 删除数据库记录
        const result = await this.ctx.database.remove(TableName, { category });

        // 删除文件
        for (const sticker of stickers) {
            try {
                await unlink(sticker.filePath);
                this.logger.debug(`已删除表情包文件: ${sticker.filePath}`);
            } catch (error) {
                this.logger.warn(`删除文件失败: ${sticker.filePath}`, error);
            }
        }

        this.logger.info(`已删除分类 "${category}"，共移除 ${result.removed} 个表情包`);
        return result.removed;
    }

    /**
     * 合并两个分类
     */
    public async mergeCategories(sourceCategory: string, targetCategory: string): Promise<number> {
        const result = await this.ctx.database.set(TableName, { category: sourceCategory }, { category: targetCategory });

        this.logger.info(`已将分类 "${sourceCategory}" 合并到 "${targetCategory}"，移动了 ${result.modified} 个表情包`);
        return result.modified;
    }

    /**
     * 移动表情包到新分类
     */
    public async moveSticker(stickerId: string, newCategory: string): Promise<number> {
        const result = await this.ctx.database.set(TableName, { id: stickerId }, { category: newCategory });

        if (result.modified === 0) {
            throw new Error("未找到该表情包");
        }

        this.logger.info(`已将表情包 ${stickerId} 移动到分类 "${newCategory}"`);
        return result.modified;
    }

    /**
     * 获取分类中的表情包数量
     */
    public async getStickerCount(category: string): Promise<number> {
        const result = await this.ctx.database.get(TableName, {
            category: { $eq: category },
        });

        return result.length;
    }

    /**
     * 获取指定表情包
     */
    public async getSticker(stickerId: string): Promise<StickerRecord | null> {
        const result = await this.ctx.database.get(TableName, { id: stickerId });
        return result.length > 0 ? result[0] : null;
    }

    /**
     * 清理未使用的表情包
     */
    public async cleanupUnreferenced(): Promise<number> {
        const dbFiles = new Set((await this.ctx.database.select(TableName).execute()).map((r) => path.basename(r.filePath)));
        const fsFiles = await readdir(this.config.storagePath);

        let deletedCount = 0;
        for (const file of fsFiles) {
            if (!dbFiles.has(file)) {
                try {
                    await unlink(path.join(this.config.storagePath, file));
                    this.logger.debug(`清理未引用表情: ${file}`);
                    deletedCount++;
                } catch (error) {
                    this.logger.warn(`清理失败: ${file}`, error);
                }
            }
        }

        return deletedCount;
    }
}

interface ImportStats {
    total: number; // 总尝试导入数
    success: number; // 成功导入数
    failed: number; // 导入失败数
    skipped: number; // 跳过数（重复表情包）
    failedFiles?: string[]; // 失败的文件名列表
    failedUrls?: {
        // 失败的 URL 列表
        url: string;
        error: string;
    }[];
}
