/**
 * TaskService - Task Management Service
 *
 * Provides CRUD operations and task management for projects.
 * Supports multi-tenant architecture with ServiceContext pattern.
 *
 * @module Projects
 */

import { DataSource, Repository, IsNull } from 'typeorm';
import { TaskEntity, TaskStatus, TaskPriority } from '../entities/task.entity';
import { TimesheetEntity } from '../entities/timesheet.entity';
import { ServiceContext, PaginatedResult } from './project.service';

export interface CreateTaskDto {
  projectId: string;
  stageId?: string;
  parentId?: string;
  name: string;
  description?: string;
  assignedTo?: string;
  dateDeadline?: string;
  estimatedHours?: number;
  priority?: TaskPriority;
  color?: string;
}

export interface UpdateTaskDto {
  stageId?: string | null;
  parentId?: string | null;
  name?: string;
  description?: string | null;
  assignedTo?: string | null;
  dateDeadline?: string | null;
  estimatedHours?: number | null;
  priority?: TaskPriority;
  status?: TaskStatus;
  sequence?: number;
  color?: string | null;
}

export interface TaskFilters {
  projectId?: string;
  stageId?: string;
  parentId?: string;
  assignedTo?: string;
  status?: TaskStatus;
  priority?: TaskPriority;
  search?: string;
}

export interface TaskWithHours extends TaskEntity {
  spentHours?: number;
}

export class TaskService {
  private repository: Repository<TaskEntity>;
  private timesheetRepository: Repository<TimesheetEntity>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(TaskEntity);
    this.timesheetRepository = dataSource.getRepository(TimesheetEntity);
  }

  /**
   * Find task by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<TaskEntity | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Find task by ID with spent hours
   */
  async findByIdWithHours(ctx: ServiceContext, id: string): Promise<TaskWithHours | null> {
    const task = await this.findById(ctx, id);
    if (!task) {
      return null;
    }

    const hoursResult = await this.timesheetRepository
      .createQueryBuilder('t')
      .select('SUM(t.hours)', 'total')
      .where('t.task_id = :taskId', { taskId: id })
      .andWhere('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .getRawOne();

    return {
      ...task,
      spentHours: parseFloat(hoursResult?.total || '0'),
    };
  }

  /**
   * Find all tasks with pagination and filters
   */
  async findAll(
    ctx: ServiceContext,
    filters: TaskFilters = {},
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<TaskEntity>> {
    const skip = (page - 1) * limit;

    const qb = this.repository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL');

    if (filters.projectId) {
      qb.andWhere('t.project_id = :projectId', { projectId: filters.projectId });
    }
    if (filters.stageId) {
      qb.andWhere('t.stage_id = :stageId', { stageId: filters.stageId });
    }
    if (filters.parentId) {
      qb.andWhere('t.parent_id = :parentId', { parentId: filters.parentId });
    }
    if (filters.assignedTo) {
      qb.andWhere('t.assigned_to = :assignedTo', { assignedTo: filters.assignedTo });
    }
    if (filters.status) {
      qb.andWhere('t.status = :status', { status: filters.status });
    }
    if (filters.priority) {
      qb.andWhere('t.priority = :priority', { priority: filters.priority });
    }
    if (filters.search) {
      qb.andWhere('t.name ILIKE :search', { search: `%${filters.search}%` });
    }

    qb.orderBy('t.sequence', 'ASC').addOrderBy('t.created_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Create a new task
   */
  async create(ctx: ServiceContext, dto: CreateTaskDto): Promise<TaskEntity> {
    // Get next sequence for project
    const maxSeq = await this.repository
      .createQueryBuilder('t')
      .select('COALESCE(MAX(t.sequence), 0) + 1', 'nextSeq')
      .where('t.project_id = :projectId', { projectId: dto.projectId })
      .andWhere('t.deleted_at IS NULL')
      .getRawOne();

    const entity = this.repository.create({
      tenantId: ctx.tenantId,
      projectId: dto.projectId,
      stageId: dto.stageId || null,
      parentId: dto.parentId || null,
      name: dto.name,
      description: dto.description || null,
      assignedTo: dto.assignedTo || null,
      dateDeadline: dto.dateDeadline ? new Date(dto.dateDeadline) : null,
      estimatedHours: dto.estimatedHours || 0,
      priority: dto.priority || TaskPriority.NORMAL,
      status: TaskStatus.TODO,
      sequence: parseInt(maxSeq?.nextSeq || '1'),
      color: dto.color || null,
      createdBy: ctx.userId || null,
    });

    return this.repository.save(entity);
  }

  /**
   * Update a task
   */
  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateTaskDto
  ): Promise<TaskEntity | null> {
    const entity = await this.findById(ctx, id);
    if (!entity) {
      return null;
    }

    // Prevent circular parent reference
    if (dto.parentId !== undefined && dto.parentId === id) {
      throw new Error('A task cannot be its own parent');
    }

    if (dto.stageId !== undefined) entity.stageId = dto.stageId;
    if (dto.parentId !== undefined) entity.parentId = dto.parentId;
    if (dto.name !== undefined) entity.name = dto.name;
    if (dto.description !== undefined) entity.description = dto.description;
    if (dto.assignedTo !== undefined) entity.assignedTo = dto.assignedTo;
    if (dto.dateDeadline !== undefined) {
      entity.dateDeadline = dto.dateDeadline ? new Date(dto.dateDeadline) : null;
    }
    if (dto.estimatedHours !== undefined) {
      entity.estimatedHours = dto.estimatedHours ?? 0;
    }
    if (dto.priority !== undefined) entity.priority = dto.priority;
    if (dto.status !== undefined) entity.status = dto.status;
    if (dto.sequence !== undefined) entity.sequence = dto.sequence;
    if (dto.color !== undefined) entity.color = dto.color;

    entity.updatedBy = ctx.userId || null;

    return this.repository.save(entity);
  }

  /**
   * Soft delete a task
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const entity = await this.findById(ctx, id);
    if (!entity) {
      return false;
    }

    entity.deletedAt = new Date();
    entity.deletedBy = ctx.userId || null;
    await this.repository.save(entity);
    return true;
  }

  /**
   * Move task to a different stage and sequence
   */
  async move(
    ctx: ServiceContext,
    id: string,
    stageId: string | null,
    sequence: number
  ): Promise<TaskEntity | null> {
    return this.update(ctx, id, { stageId, sequence });
  }

  /**
   * Assign task to a user
   */
  async assign(
    ctx: ServiceContext,
    id: string,
    userId: string | null
  ): Promise<TaskEntity | null> {
    return this.update(ctx, id, { assignedTo: userId });
  }

  /**
   * Change task status
   */
  async changeStatus(
    ctx: ServiceContext,
    id: string,
    status: TaskStatus
  ): Promise<TaskEntity | null> {
    return this.update(ctx, id, { status });
  }

  /**
   * Mark task as done
   */
  async complete(ctx: ServiceContext, id: string): Promise<TaskEntity | null> {
    return this.changeStatus(ctx, id, TaskStatus.DONE);
  }

  /**
   * Mark task as in progress
   */
  async start(ctx: ServiceContext, id: string): Promise<TaskEntity | null> {
    return this.changeStatus(ctx, id, TaskStatus.IN_PROGRESS);
  }

  /**
   * Cancel a task
   */
  async cancel(ctx: ServiceContext, id: string): Promise<TaskEntity | null> {
    return this.changeStatus(ctx, id, TaskStatus.CANCELLED);
  }

  /**
   * Find tasks by project
   */
  async findByProject(
    ctx: ServiceContext,
    projectId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<TaskEntity>> {
    return this.findAll(ctx, { projectId }, page, limit);
  }

  /**
   * Find tasks assigned to a user
   */
  async findByAssignee(
    ctx: ServiceContext,
    assignedTo: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<TaskEntity>> {
    return this.findAll(ctx, { assignedTo }, page, limit);
  }

  /**
   * Find subtasks of a parent task
   */
  async findSubtasks(
    ctx: ServiceContext,
    parentId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<TaskEntity>> {
    return this.findAll(ctx, { parentId }, page, limit);
  }

  /**
   * Find overdue tasks
   */
  async findOverdue(
    ctx: ServiceContext,
    projectId?: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<TaskEntity>> {
    const skip = (page - 1) * limit;

    const qb = this.repository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL')
      .andWhere('t.date_deadline < :today', { today: new Date() })
      .andWhere('t.status NOT IN (:...completedStatuses)', {
        completedStatuses: [TaskStatus.DONE, TaskStatus.CANCELLED],
      });

    if (projectId) {
      qb.andWhere('t.project_id = :projectId', { projectId });
    }

    qb.orderBy('t.date_deadline', 'ASC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get task with timesheets
   */
  async findWithTimesheets(ctx: ServiceContext, id: string): Promise<TaskEntity | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
      relations: ['timesheets'],
    });
  }

  /**
   * Duplicate a task
   */
  async duplicate(
    ctx: ServiceContext,
    id: string,
    newName?: string
  ): Promise<TaskEntity | null> {
    const original = await this.findById(ctx, id);
    if (!original) {
      return null;
    }

    return this.create(ctx, {
      projectId: original.projectId,
      stageId: original.stageId || undefined,
      name: newName || `${original.name} (copy)`,
      description: original.description || undefined,
      priority: original.priority,
      estimatedHours: original.estimatedHours,
      color: original.color || undefined,
    });
  }
}
