import { Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common';
import { I18nService } from '../i18n/i18n.service';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import { KnowledgeGroup } from './knowledge-group.entity';
import { KnowledgeBase } from '../knowledge-base/knowledge-base.entity';
import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';

export interface CreateGroupDto {
  name: string;
  description?: string;
  color?: string;
  parentId?: string | null;
}

export interface UpdateGroupDto {
  name?: string;
  description?: string;
  color?: string;
  parentId?: string | null;
}

export interface GroupWithFileCount {
  id: string;
  name: string;
  description?: string;
  color: string;
  fileCount: number;
  parentId?: string | null;
  children?: GroupWithFileCount[];
  createdAt: Date;
}

@Injectable()
export class KnowledgeGroupService {
  constructor(
    @InjectRepository(KnowledgeGroup)
    private groupRepository: Repository<KnowledgeGroup>,
    @InjectRepository(KnowledgeBase)
    private knowledgeBaseRepository: Repository<KnowledgeBase>,
    @Inject(forwardRef(() => KnowledgeBaseService))
    private knowledgeBaseService: KnowledgeBaseService,
    private i18nService: I18nService,
  ) { }

  async findAll(userId: string, tenantId: string): Promise<GroupWithFileCount[]> {
    // Return all groups for the tenant with file counts
    const groups = await this.groupRepository
      .createQueryBuilder('group')
      .leftJoin('group.knowledgeBases', 'kb')
      .where('group.tenantId = :tenantId', { tenantId })
      .addSelect('COUNT(kb.id)', 'fileCount')
      .groupBy('group.id')
      .orderBy('group.createdAt', 'ASC')
      .getRawAndEntities();

    const flatList: GroupWithFileCount[] = groups.entities.map((group, index) => ({
      id: group.id,
      name: group.name,
      description: group.description,
      color: group.color,
      parentId: group.parentId ?? null,
      fileCount: parseInt(groups.raw[index].fileCount) || 0,
      createdAt: group.createdAt,
      children: [],
    }));

    // Build tree structure
    return this.buildTree(flatList);
  }

  /** Build a nested tree from a flat list */
  private buildTree(items: GroupWithFileCount[]): GroupWithFileCount[] {
    const map = new Map<string, GroupWithFileCount>();
    items.forEach(item => map.set(item.id, { ...item, children: [] }));

    const roots: GroupWithFileCount[] = [];
    map.forEach(item => {
      if (item.parentId && map.has(item.parentId)) {
        map.get(item.parentId)!.children!.push(item);
      } else {
        roots.push(item);
      }
    });
    return roots;
  }

  async findOne(id: string, userId: string, tenantId: string): Promise<KnowledgeGroup> {
    // Restrict group to tenant
    const group = await this.groupRepository.findOne({
      where: { id, tenantId },
      relations: ['knowledgeBases'],
    });

    if (!group) {
      throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
    }

    return group;
  }

  async create(userId: string, tenantId: string, createGroupDto: CreateGroupDto): Promise<KnowledgeGroup> {
    const group = this.groupRepository.create({
      ...createGroupDto,
      parentId: createGroupDto.parentId ?? null,
      tenantId,
    });

    return await this.groupRepository.save(group);
  }

  async update(id: string, userId: string, tenantId: string, updateGroupDto: UpdateGroupDto): Promise<KnowledgeGroup> {
    // Update group within the tenant
    const group = await this.groupRepository.findOne({
      where: { id, tenantId },
    });

    if (!group) {
      throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
    }

    Object.assign(group, updateGroupDto);
    return await this.groupRepository.save(group);
  }

  async remove(id: string, userId: string, tenantId: string): Promise<void> {
    const group = await this.groupRepository.findOne({
      where: { id, tenantId },
    });

    if (!group) {
      throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
    }

    // Recursively delete this group and all its descendants
    await this.removeGroupRecursive(id, userId, tenantId);
  }

  /** Recursively delete a group, all its children, and all associated files */
  private async removeGroupRecursive(id: string, userId: string, tenantId: string): Promise<void> {
    // 1. Find all direct children of this group
    const children = await this.groupRepository.find({ where: { parentId: id, tenantId } });

    // 2. Recurse into each child first (depth-first)
    for (const child of children) {
      await this.removeGroupRecursive(child.id, userId, tenantId);
    }

    // 3. Delete all files belonging to this group
    const files = await this.knowledgeBaseRepository
      .createQueryBuilder('kb')
      .innerJoin('kb.groups', 'group')
      .where('group.id = :groupId', { groupId: id })
      .select('kb.id')
      .getMany();

    for (const file of files) {
      try {
        const fullFile = await this.knowledgeBaseRepository.findOne({
          where: { id: file.id },
          select: ['id', 'userId', 'tenantId'],
        });
        if (fullFile) {
          await this.knowledgeBaseService.deleteFile(fullFile.id, fullFile.userId, fullFile.tenantId as string);
        }
      } catch (error) {
        console.error(`Failed to delete file ${file.id} when deleting group ${id}`, error);
      }
    }

    // 4. Delete the group itself
    const group = await this.groupRepository.findOne({ where: { id } });
    if (group) {
      await this.groupRepository.remove(group);
    }
  }

  async getGroupFiles(groupId: string, userId: string, tenantId: string): Promise<KnowledgeBase[]> {
    const group = await this.groupRepository.findOne({
      where: { id: groupId, tenantId },
      relations: ['knowledgeBases'],
    });

    if (!group) {
      throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
    }

    return group.knowledgeBases;
  }

  async addFilesToGroup(fileId: string, groupIds: string[], userId: string, tenantId: string): Promise<void> {
    const file = await this.knowledgeBaseRepository.findOne({
      where: { id: fileId, userId, tenantId },
      relations: ['groups'],
    });

    if (!file) {
      throw new NotFoundException(this.i18nService.getMessage('fileNotFound'));
    }

    // Load all groups by ID without user restriction
    const groups = await this.groupRepository.findBy({ id: In(groupIds) });
    const validGroups = groups.filter(g => g.tenantId === tenantId);

    if (groupIds.length > 0 && validGroups.length !== groupIds.length) {
      throw new NotFoundException(this.i18nService.getMessage('someGroupsNotFound'));
    }

    file.groups = validGroups;
    await this.knowledgeBaseRepository.save(file);
  }

  async removeFileFromGroup(fileId: string, groupId: string, userId: string, tenantId: string): Promise<void> {
    const file = await this.knowledgeBaseRepository.findOne({
      where: { id: fileId, userId, tenantId },
      relations: ['groups'],
    });

    if (!file) {
      throw new NotFoundException(this.i18nService.getMessage('fileNotFound'));
    }

    file.groups = file.groups.filter(group => group.id !== groupId);
    await this.knowledgeBaseRepository.save(file);
  }

  async getFileIdsByGroups(groupIds: string[], userId: string, tenantId: string): Promise<string[]> {
    if (!groupIds || groupIds.length === 0) {
      return [];
    }

    const result = await this.knowledgeBaseRepository
      .createQueryBuilder('kb')
      .innerJoin('kb.groups', 'group')
      .where('group.id IN (:...groupIds)', { groupIds })
      .andWhere('kb.tenantId = :tenantId', { tenantId })
      .select('DISTINCT kb.id', 'id')
      .getRawMany();

    return result.map(row => row.id);
  }

  /**
   * Find or create a group by name and parentId within a tenant.
   * Used by import tasks to build folder hierarchy.
   */
  async findOrCreate(
    userId: string,
    tenantId: string,
    name: string,
    parentId: string | null,
    description?: string,
  ): Promise<KnowledgeGroup> {
    const existing = await this.groupRepository.findOne({
      where: { name, tenantId, parentId: parentId ?? undefined },
    });
    if (existing) return existing;

    return this.create(userId, tenantId, { name, description, parentId });
  }
}