/**
 * Departments Service
 * @module HR
 */

import { Repository, FindOptionsWhere, ILike, IsNull } from 'typeorm';
import { Department } from '../entities/department.entity';
import { CreateDepartmentDto, UpdateDepartmentDto } from '../dto';

export interface DepartmentSearchParams {
  tenantId: string;
  companyId?: string;
  search?: string;
  isActive?: boolean;
  parentId?: string | null;
  limit?: number;
  offset?: number;
}

export class DepartmentsService {
  constructor(private readonly departmentRepository: Repository<Department>) {}

  /**
   * Find all departments with filters
   */
  async findAll(params: DepartmentSearchParams): Promise<{ data: Department[]; total: number }> {
    const {
      tenantId,
      companyId,
      search,
      isActive,
      parentId,
      limit = 50,
      offset = 0,
    } = params;

    const queryBuilder = this.departmentRepository
      .createQueryBuilder('department')
      .leftJoinAndSelect('department.parent', 'parent')
      .leftJoinAndSelect('department.children', 'children')
      .where('department.tenant_id = :tenantId', { tenantId });

    if (companyId) {
      queryBuilder.andWhere('department.company_id = :companyId', { companyId });
    }

    if (search) {
      queryBuilder.andWhere(
        '(department.name ILIKE :search OR department.code ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    if (isActive !== undefined) {
      queryBuilder.andWhere('department.is_active = :isActive', { isActive });
    }

    if (parentId === null) {
      queryBuilder.andWhere('department.parent_id IS NULL');
    } else if (parentId) {
      queryBuilder.andWhere('department.parent_id = :parentId', { parentId });
    }

    queryBuilder.orderBy('department.name', 'ASC');

    const total = await queryBuilder.getCount();
    const data = await queryBuilder.skip(offset).take(limit).getMany();

    return { data, total };
  }

  /**
   * Find one department by ID
   */
  async findOne(id: string, tenantId: string): Promise<Department | null> {
    return this.departmentRepository.findOne({
      where: { id, tenantId },
      relations: ['parent', 'children'],
    });
  }

  /**
   * Find department by code
   */
  async findByCode(code: string, tenantId: string, companyId: string): Promise<Department | null> {
    return this.departmentRepository.findOne({
      where: { code, tenantId, companyId },
    });
  }

  /**
   * Create new department
   */
  async create(tenantId: string, dto: CreateDepartmentDto): Promise<Department> {
    // Check for existing code
    if (dto.code) {
      const existingCode = await this.findByCode(dto.code, tenantId, dto.companyId);
      if (existingCode) {
        throw new Error('Ya existe un departamento con este codigo');
      }
    }

    // Verify parent exists if provided
    if (dto.parentId) {
      const parent = await this.findOne(dto.parentId, tenantId);
      if (!parent) {
        throw new Error('Departamento padre no encontrado');
      }
    }

    const department = this.departmentRepository.create({
      ...dto,
      tenantId,
    });

    return this.departmentRepository.save(department);
  }

  /**
   * Update department
   */
  async update(id: string, tenantId: string, dto: UpdateDepartmentDto): Promise<Department | null> {
    const department = await this.findOne(id, tenantId);
    if (!department) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== department.code) {
      const existing = await this.findByCode(dto.code, tenantId, department.companyId);
      if (existing) {
        throw new Error('Ya existe un departamento con este codigo');
      }
    }

    // Verify parent exists if provided
    if (dto.parentId !== undefined) {
      if (dto.parentId) {
        // Cannot set self as parent
        if (dto.parentId === id) {
          throw new Error('Un departamento no puede ser su propio padre');
        }

        const parent = await this.findOne(dto.parentId, tenantId);
        if (!parent) {
          throw new Error('Departamento padre no encontrado');
        }

        // Check for circular reference
        if (await this.wouldCreateCircularReference(id, dto.parentId, tenantId)) {
          throw new Error('No se puede crear referencia circular en jerarquia');
        }
      }
    }

    Object.assign(department, dto);
    return this.departmentRepository.save(department);
  }

  /**
   * Check if setting parentId would create a circular reference
   */
  private async wouldCreateCircularReference(
    departmentId: string,
    newParentId: string,
    tenantId: string
  ): Promise<boolean> {
    let currentId: string | null = newParentId;

    while (currentId) {
      if (currentId === departmentId) {
        return true;
      }

      const parent = await this.departmentRepository.findOne({
        where: { id: currentId, tenantId },
        select: ['parentId'],
      });

      currentId = parent?.parentId || null;
    }

    return false;
  }

  /**
   * Delete department
   */
  async delete(id: string, tenantId: string): Promise<boolean> {
    const department = await this.findOne(id, tenantId);
    if (!department) return false;

    // Check if has children
    const childrenCount = await this.departmentRepository.count({
      where: { parentId: id, tenantId },
    });

    if (childrenCount > 0) {
      throw new Error('No se puede eliminar un departamento con subdepartamentos');
    }

    const result = await this.departmentRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  /**
   * Get root departments (no parent)
   */
  async getRootDepartments(tenantId: string, companyId?: string): Promise<Department[]> {
    const where: FindOptionsWhere<Department> = {
      tenantId,
      parentId: IsNull(),
      isActive: true,
    };

    if (companyId) {
      where.companyId = companyId;
    }

    return this.departmentRepository.find({
      where,
      relations: ['children'],
      order: { name: 'ASC' },
    });
  }

  /**
   * Get department hierarchy as tree
   */
  async getHierarchy(tenantId: string, companyId?: string): Promise<Department[]> {
    const roots = await this.getRootDepartments(tenantId, companyId);
    return this.buildTreeWithChildren(roots, tenantId);
  }

  /**
   * Build tree recursively with children
   */
  private async buildTreeWithChildren(
    departments: Department[],
    tenantId: string
  ): Promise<Department[]> {
    for (const dept of departments) {
      if (dept.children && dept.children.length > 0) {
        dept.children = await this.buildTreeWithChildren(dept.children, tenantId);
      }
    }
    return departments;
  }

  /**
   * Get all children of a department (recursive)
   */
  async getChildren(id: string, tenantId: string, recursive: boolean = false): Promise<Department[]> {
    const direct = await this.departmentRepository.find({
      where: { parentId: id, tenantId, isActive: true },
      order: { name: 'ASC' },
    });

    if (!recursive) {
      return direct;
    }

    const allChildren: Department[] = [...direct];
    for (const child of direct) {
      const grandChildren = await this.getChildren(child.id, tenantId, true);
      allChildren.push(...grandChildren);
    }

    return allChildren;
  }

  /**
   * Get all parent departments (ancestors)
   */
  async getAncestors(id: string, tenantId: string): Promise<Department[]> {
    const ancestors: Department[] = [];
    let current = await this.findOne(id, tenantId);

    while (current?.parentId) {
      const parent = await this.findOne(current.parentId, tenantId);
      if (parent) {
        ancestors.push(parent);
        current = parent;
      } else {
        break;
      }
    }

    return ancestors.reverse(); // Return from root to immediate parent
  }

  /**
   * Activate department
   */
  async activate(id: string, tenantId: string): Promise<Department | null> {
    const department = await this.findOne(id, tenantId);
    if (!department) return null;

    department.isActive = true;
    return this.departmentRepository.save(department);
  }

  /**
   * Deactivate department
   */
  async deactivate(id: string, tenantId: string): Promise<Department | null> {
    const department = await this.findOne(id, tenantId);
    if (!department) return null;

    department.isActive = false;
    return this.departmentRepository.save(department);
  }

  /**
   * Set department manager
   */
  async setManager(id: string, tenantId: string, managerId: string | null): Promise<Department | null> {
    const department = await this.findOne(id, tenantId);
    if (!department) return null;

    department.managerId = managerId;
    return this.departmentRepository.save(department);
  }

  /**
   * Get active departments
   */
  async getActive(tenantId: string, companyId?: string): Promise<Department[]> {
    const where: FindOptionsWhere<Department> = {
      tenantId,
      isActive: true,
    };

    if (companyId) {
      where.companyId = companyId;
    }

    return this.departmentRepository.find({
      where,
      order: { name: 'ASC' },
    });
  }
}
