/**
 * ProjectService - Generic Project Management Service
 *
 * Provides CRUD operations and lifecycle management for projects.
 * Supports multi-tenant architecture with ServiceContext pattern.
 *
 * @module Projects
 */

import { DataSource, Repository, IsNull } from 'typeorm';
import { ProjectEntity, ProjectStatus, ProjectPrivacy } from '../entities/project.entity';
import { TaskEntity } from '../entities/task.entity';
import { TimesheetEntity } from '../entities/timesheet.entity';
import { MilestoneEntity } from '../entities/milestone.entity';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateProjectDto {
  companyId: string;
  name: string;
  code?: string;
  description?: string;
  managerId?: string;
  partnerId?: string;
  analyticAccountId?: string;
  dateStart?: string;
  dateEnd?: string;
  privacy?: ProjectPrivacy;
  allowTimesheets?: boolean;
  color?: string;
}

export interface UpdateProjectDto {
  name?: string;
  code?: string | null;
  description?: string | null;
  managerId?: string | null;
  partnerId?: string | null;
  analyticAccountId?: string | null;
  dateStart?: string | null;
  dateEnd?: string | null;
  status?: ProjectStatus;
  privacy?: ProjectPrivacy;
  allowTimesheets?: boolean;
  color?: string | null;
}

export interface ProjectFilters {
  companyId?: string;
  managerId?: string;
  partnerId?: string;
  status?: ProjectStatus;
  search?: string;
}

export interface ProjectStats {
  totalTasks: number;
  completedTasks: number;
  inProgressTasks: number;
  completionPercentage: number;
  totalHours: number;
  totalMilestones: number;
  completedMilestones: number;
}

export class ProjectService {
  private repository: Repository<ProjectEntity>;
  private taskRepository: Repository<TaskEntity>;
  private timesheetRepository: Repository<TimesheetEntity>;
  private milestoneRepository: Repository<MilestoneEntity>;

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

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

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

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

    if (filters.companyId) {
      qb.andWhere('p.company_id = :companyId', { companyId: filters.companyId });
    }
    if (filters.managerId) {
      qb.andWhere('p.manager_id = :managerId', { managerId: filters.managerId });
    }
    if (filters.partnerId) {
      qb.andWhere('p.partner_id = :partnerId', { partnerId: filters.partnerId });
    }
    if (filters.status) {
      qb.andWhere('p.status = :status', { status: filters.status });
    }
    if (filters.search) {
      qb.andWhere('(p.name ILIKE :search OR p.code ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    qb.orderBy('p.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 project
   */
  async create(ctx: ServiceContext, dto: CreateProjectDto): Promise<ProjectEntity> {
    const entity = this.repository.create({
      tenantId: ctx.tenantId,
      companyId: dto.companyId,
      name: dto.name,
      code: dto.code || null,
      description: dto.description || null,
      managerId: dto.managerId || null,
      partnerId: dto.partnerId || null,
      analyticAccountId: dto.analyticAccountId || null,
      dateStart: dto.dateStart ? new Date(dto.dateStart) : null,
      dateEnd: dto.dateEnd ? new Date(dto.dateEnd) : null,
      privacy: dto.privacy || ProjectPrivacy.PUBLIC,
      allowTimesheets: dto.allowTimesheets ?? true,
      color: dto.color || null,
      status: ProjectStatus.DRAFT,
      createdBy: ctx.userId || null,
    });

    return this.repository.save(entity);
  }

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

    if (dto.name !== undefined) entity.name = dto.name;
    if (dto.code !== undefined) entity.code = dto.code;
    if (dto.description !== undefined) entity.description = dto.description;
    if (dto.managerId !== undefined) entity.managerId = dto.managerId;
    if (dto.partnerId !== undefined) entity.partnerId = dto.partnerId;
    if (dto.analyticAccountId !== undefined) entity.analyticAccountId = dto.analyticAccountId;
    if (dto.dateStart !== undefined) {
      entity.dateStart = dto.dateStart ? new Date(dto.dateStart) : null;
    }
    if (dto.dateEnd !== undefined) {
      entity.dateEnd = dto.dateEnd ? new Date(dto.dateEnd) : null;
    }
    if (dto.status !== undefined) entity.status = dto.status;
    if (dto.privacy !== undefined) entity.privacy = dto.privacy;
    if (dto.allowTimesheets !== undefined) entity.allowTimesheets = dto.allowTimesheets;
    if (dto.color !== undefined) entity.color = dto.color;

    entity.updatedBy = ctx.userId || null;

    return this.repository.save(entity);
  }

  /**
   * Soft delete a project
   */
  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;
  }

  /**
   * Get project with relations (tasks, milestones, members)
   */
  async findWithRelations(ctx: ServiceContext, id: string): Promise<ProjectEntity | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
      relations: ['tasks', 'milestones', 'members'],
    });
  }

  /**
   * Get project statistics
   */
  async getStats(ctx: ServiceContext, id: string): Promise<ProjectStats | null> {
    const project = await this.findById(ctx, id);
    if (!project) {
      return null;
    }

    // Get task counts
    const totalTasks = await this.taskRepository.count({
      where: { projectId: id, tenantId: ctx.tenantId, deletedAt: IsNull() },
    });

    const completedTasks = await this.taskRepository.count({
      where: { projectId: id, tenantId: ctx.tenantId, deletedAt: IsNull(), status: 'done' as any },
    });

    const inProgressTasks = await this.taskRepository.count({
      where: { projectId: id, tenantId: ctx.tenantId, deletedAt: IsNull(), status: 'in_progress' as any },
    });

    // Get total hours from timesheets
    const hoursResult = await this.timesheetRepository
      .createQueryBuilder('t')
      .select('SUM(t.hours)', 'total')
      .where('t.project_id = :projectId', { projectId: id })
      .andWhere('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .getRawOne();

    const totalHours = parseFloat(hoursResult?.total || '0');

    // Get milestone counts
    const totalMilestones = await this.milestoneRepository.count({
      where: { projectId: id, tenantId: ctx.tenantId },
    });

    const completedMilestones = await this.milestoneRepository.count({
      where: { projectId: id, tenantId: ctx.tenantId, status: 'completed' as any },
    });

    return {
      totalTasks,
      completedTasks,
      inProgressTasks,
      completionPercentage: totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0,
      totalHours,
      totalMilestones,
      completedMilestones,
    };
  }

  /**
   * Activate a project (change status to active)
   */
  async activate(ctx: ServiceContext, id: string): Promise<ProjectEntity | null> {
    return this.update(ctx, id, { status: ProjectStatus.ACTIVE });
  }

  /**
   * Complete a project
   */
  async complete(ctx: ServiceContext, id: string): Promise<ProjectEntity | null> {
    return this.update(ctx, id, { status: ProjectStatus.COMPLETED });
  }

  /**
   * Cancel a project
   */
  async cancel(ctx: ServiceContext, id: string): Promise<ProjectEntity | null> {
    return this.update(ctx, id, { status: ProjectStatus.CANCELLED });
  }

  /**
   * Put project on hold
   */
  async hold(ctx: ServiceContext, id: string): Promise<ProjectEntity | null> {
    return this.update(ctx, id, { status: ProjectStatus.ON_HOLD });
  }

  /**
   * Find projects by company
   */
  async findByCompany(
    ctx: ServiceContext,
    companyId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<ProjectEntity>> {
    return this.findAll(ctx, { companyId }, page, limit);
  }

  /**
   * Find projects by manager
   */
  async findByManager(
    ctx: ServiceContext,
    managerId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<ProjectEntity>> {
    return this.findAll(ctx, { managerId }, page, limit);
  }

  /**
   * Find active projects
   */
  async findActive(
    ctx: ServiceContext,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<ProjectEntity>> {
    return this.findAll(ctx, { status: ProjectStatus.ACTIVE }, page, limit);
  }

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

    return this.create(ctx, {
      companyId: original.companyId,
      name: newName,
      code: newCode,
      description: original.description || undefined,
      managerId: original.managerId || undefined,
      partnerId: original.partnerId || undefined,
      analyticAccountId: original.analyticAccountId || undefined,
      privacy: original.privacy,
      allowTimesheets: original.allowTimesheets,
      color: original.color || undefined,
    });
  }
}
