import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThanOrEqual } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { ImportTask } from './import-task.entity';
import { Cron, CronExpression } from '@nestjs/schedule';
import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';
import { KnowledgeGroupService } from '../knowledge-group/knowledge-group.service';
import { I18nService } from '../i18n/i18n.service';
import * as fs from 'fs';
import * as path from 'path';

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

    constructor(
        @InjectRepository(ImportTask)
        private taskRepository: Repository<ImportTask>,
        private kbService: KnowledgeBaseService,
        private groupService: KnowledgeGroupService,
        private configService: ConfigService,
        private i18nService: I18nService,
    ) { }

    async create(taskData: Partial<ImportTask>): Promise<ImportTask> {
        const task = this.taskRepository.create(taskData);
        const savedTask = await this.taskRepository.save(task);

        // If no scheduled time or scheduled time is in the past, execute immediately (async)
        if (!task.scheduledAt || task.scheduledAt <= new Date()) {
            this.executeTask(savedTask.id).catch(err =>
                this.logger.error(`Immediate execution failed to start for task ${savedTask.id}`, err)
            );
        }

        return savedTask;
    }

    async findAll(userId: string): Promise<ImportTask[]> {
        return this.taskRepository.find({
            where: { userId },
            order: { createdAt: 'DESC' },
        });
    }

    async delete(taskId: string, userId: string): Promise<void> {
        const task = await this.taskRepository.findOne({ where: { id: taskId, userId } });
        if (!task) {
            throw new Error(this.i18nService.getMessage('importTaskNotFound'));
        }
        await this.taskRepository.remove(task);
    }

    @Cron(CronExpression.EVERY_MINUTE)
    async handleScheduledTasks() {
        this.logger.debug('Checking for scheduled import tasks...');
        const now = new Date();

        const tasks = await this.taskRepository.find({
            where: {
                status: 'PENDING',
                scheduledAt: LessThanOrEqual(now),
            },
        });

        for (const task of tasks) {
            this.logger.log(`Starting scheduled task ${task.id}`);
            this.executeTask(task.id).catch(err =>
                this.logger.error(`Scheduled execution failed to start for task ${task.id}`, err)
            );
        }
    }

    private async executeTask(taskId: string) {
        this.logger.debug(`Executing task ${taskId}`);
        const task = await this.taskRepository.findOne({ where: { id: taskId } });
        if (!task) {
            this.logger.warn(`Task ${taskId} not found.`);
            return;
        }

        if (task.status === 'PROCESSING' || task.status === 'COMPLETED') {
            this.logger.debug(`Task ${taskId} is already ${task.status}, skipping execution.`);
            return;
        }

        await this.updateStatus(taskId, 'PROCESSING', 'Starting import...');

        try {
            if (!fs.existsSync(task.sourcePath)) {
                throw new Error(this.i18nService.formatMessage('sourcePathNotFound', { path: task.sourcePath }));
            }

            const uploadPath = this.configService.get<string>('UPLOAD_FILE_PATH', './uploads');
            const importTargetDir = path.join(uploadPath, 'imported', taskId);

            if (!fs.existsSync(importTargetDir)) {
                fs.mkdirSync(importTargetDir, { recursive: true });
            }

            let successCount = 0;
            let failCount = 0;

            if (task.useHierarchy) {
                // ---- Hierarchy mode: create sub-groups matching folder structure ----
                await this.appendLog(taskId, `Scanning directory with hierarchy: ${task.sourcePath}`);

                // Determine root group
                let rootGroupId = task.targetGroupId;
                if (!rootGroupId) {
                    const rootName = task.targetGroupName || path.basename(task.sourcePath);
                    const rootGroup = await this.groupService.create(task.userId, task.tenantId || 'default', {
                        name: rootName,
                        description: `Imported from ${task.sourcePath}`,
                        color: '#0078D4',
                    });
                    rootGroupId = rootGroup.id;
                    await this.appendLog(taskId, `Created root group: ${rootName}`);
                }

                // Map from relative dir path -> groupId
                const dirToGroupId = new Map<string, string>();
                dirToGroupId.set('.', rootGroupId);

                // Collect all files first
                const allFiles = this.scanDir(task.sourcePath);
                await this.appendLog(taskId, `Found ${allFiles.length} files.`);

                for (let i = 0; i < allFiles.length; i++) {
                    const filePath = allFiles[i];
                    const relativeDir = path.relative(task.sourcePath, path.dirname(filePath));
                    const normalizedDir = relativeDir || '.';

                    // Ensure group exists for this directory
                    const groupId = await this.ensureHierarchyGroup(
                        task.userId,
                        task.tenantId || 'default',
                        normalizedDir,
                        dirToGroupId,
                        task.sourcePath,
                        taskId,
                    );

                    try {
                        const kb = await this.importSingleFile(filePath, task, importTargetDir, i, allFiles.length);
                        await this.groupService.addFilesToGroup(kb.id, [groupId], task.userId, task.tenantId || 'default');
                        successCount++;
                        if (successCount % 10 === 0) {
                            await this.appendLog(taskId, `Imported ${successCount} files...`);
                        }
                    } catch (e) {
                        failCount++;
                        await this.appendLog(taskId, `Failed to import ${path.basename(filePath)}: ${e.message}`);
                    }
                }
            } else {
                // ---- Single-group mode (original behavior) ----
                let groupId = task.targetGroupId;
                if (!groupId && task.targetGroupName) {
                    const group = await this.groupService.create(task.userId, task.tenantId || 'default', {
                        name: task.targetGroupName,
                        description: `Imported from ${task.sourcePath}`,
                        color: '#0078D4',
                    });
                    groupId = group.id;
                    await this.appendLog(taskId, `Created new group: ${task.targetGroupName}`);
                } else if (!groupId) {
                    throw new Error(this.i18nService.getMessage('targetGroupRequired'));
                }

                await this.appendLog(taskId, `Scanning directory: ${task.sourcePath}`);
                const filesToImport = this.scanDir(task.sourcePath);
                await this.appendLog(taskId, `Found ${filesToImport.length} files.`);

                for (let i = 0; i < filesToImport.length; i++) {
                    const filePath = filesToImport[i];
                    try {
                        const kb = await this.importSingleFile(filePath, task, importTargetDir, i, filesToImport.length);
                        await this.groupService.addFilesToGroup(kb.id, [groupId], task.userId, task.tenantId || 'default');
                        successCount++;
                        if (successCount % 10 === 0) {
                            await this.appendLog(taskId, `Imported ${successCount} files...`);
                        }
                    } catch (e) {
                        failCount++;
                        await this.appendLog(taskId, `Failed to import ${path.basename(filePath)}: ${e.message}`);
                    }
                }
            }

            await this.updateStatus(taskId, 'COMPLETED', `Import finished. Success: ${successCount}, Failed: ${failCount}`);

        } catch (error) {
            await this.updateStatus(taskId, 'FAILED', `Fatal error: ${error.message}`);
        }
    }

    /**
     * Ensure a KnowledgeGroup exists for each segment of the relative directory path.
     * Returns the groupId for the leaf directory.
     */
    private async ensureHierarchyGroup(
        userId: string,
        tenantId: string,
        relativeDir: string,
        dirToGroupId: Map<string, string>,
        _sourcePath: string,
        taskId: string,
    ): Promise<string> {
        if (dirToGroupId.has(relativeDir)) {
            return dirToGroupId.get(relativeDir)!;
        }

        const segments = relativeDir.split(path.sep);
        let currentPath = '';
        let parentGroupId = dirToGroupId.get('.') ?? dirToGroupId.values().next().value;

        for (const segment of segments) {
            currentPath = currentPath ? path.join(currentPath, segment) : segment;
            if (dirToGroupId.has(currentPath)) {
                parentGroupId = dirToGroupId.get(currentPath)!;
                continue;
            }

            // Create a group for this directory segment
            const group = await this.groupService.findOrCreate(
                userId,
                tenantId,
                segment,
                parentGroupId,
                `Sub-folder: ${currentPath}`,
            );
            dirToGroupId.set(currentPath, group.id);
            await this.appendLog(taskId, `Created sub-group: ${segment}`);
            parentGroupId = group.id;
        }

        return parentGroupId;
    }

    /** Copy file to safe location and index it */
    private async importSingleFile(
        filePath: string,
        task: ImportTask,
        importTargetDir: string,
        index: number,
        total: number,
    ) {
        const filename = path.basename(filePath);
        const storedFilename = `imported-${Date.now()}-${Math.random().toString(36).substr(2, 5)}-${filename}`;
        const targetPath = path.join(importTargetDir, storedFilename);

        fs.copyFileSync(filePath, targetPath);
        const stats = fs.statSync(targetPath);

        const fileInfo = {
            filename: storedFilename,
            originalname: filename,
            path: targetPath,
            mimetype: 'text/markdown',
            size: stats.size,
        };

        const indexingConfig = {
            chunkSize: task.chunkSize || 500,
            chunkOverlap: task.chunkOverlap || 50,
            embeddingModelId: task.embeddingModelId,
            mode: (task.mode || 'fast') as 'fast' | 'precise',
        };

        this.logger.log(`Processing file ${index + 1}/${total}: ${filename}`);
        const kb = await this.kbService.createAndIndex(fileInfo, task.userId, task.tenantId || 'default', {
            ...indexingConfig,
            waitForCompletion: true,
        } as any);
        this.logger.log(`File ${index + 1}/${total} processing completed: ${filename}`);
        return kb;
    }

    private scanDir(directory: string): string[] {
        let results: string[] = [];
        const items = fs.readdirSync(directory);
        for (const item of items) {
            const fullPath = path.join(directory, item);
            const stat = fs.statSync(fullPath);
            if (stat.isDirectory()) {
                results = results.concat(this.scanDir(fullPath));
            } else {
                if (item.match(/\.(md|txt|html|json|pdf|docx|xlsx|pptx|csv)$/i)) {
                    results.push(fullPath);
                }
            }
        }
        return results;
    }

    private async updateStatus(id: string, status: ImportTask['status'], logMessage?: string) {
        const task = await this.taskRepository.findOne({ where: { id } });
        if (task) {
            task.status = status;
            if (logMessage) {
                task.logs = (task.logs || '') + `[${new Date().toISOString()}] ${logMessage}\n`;
            }
            await this.taskRepository.save(task);
        }
    }

    private async appendLog(id: string, message: string) {
        const task = await this.taskRepository.findOne({ where: { id } });
        if (task) {
            task.logs = (task.logs || '') + `[${new Date().toISOString()}] ${message}\n`;
            await this.taskRepository.save(task);
        }
    }
}
