import {
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ILike, Repository, type FindOptionsOrder } from 'typeorm';
import { UserService, type PublicUser } from '../user/user.service';
import {
  paginated,
  resolvePage,
  type PaginatedResult,
} from '../../common/pagination/pagination';
import { Project } from './entities/project.entity';
import { ProjectMember } from './entities/project-member.entity';
import { Task } from '../task/entities/task.entity';
import { CreateProjectDto } from './dto/create-project.dto';
import { UpdateProjectDto } from './dto/update-project.dto';

// 프로젝트 응답 형태 — 파생 라벨/진행률은 프론트에서 계산
export interface ProjectResponse {
  id: string;
  name: string;
  description: string | null;
  members: PublicUser[];
  memberCount: number;
  doneCount: number; // 완료 업무 수
  totalCount: number; // 전체 업무 수
  updatedAt: string;
  // 단건(findOne)에서만 — 현재 사용자 권한
  canManage?: boolean; // admin
  isOwner?: boolean; // 소유자
}

// 프로젝트 비즈니스 로직 + 권한 헬퍼(다른 모듈이 주입해 사용)
@Injectable()
export class ProjectService {
  constructor(
    @InjectRepository(Project)
    private readonly projectRepo: Repository<Project>,
    @InjectRepository(ProjectMember)
    private readonly memberRepo: Repository<ProjectMember>,
    @InjectRepository(Task)
    private readonly taskRepo: Repository<Task>,
    private readonly userService: UserService,
  ) {}

  // 프로젝트 목록 — 단일 조직 모델상 인증 사용자면 전체 열람. 검색/정렬 + 페이지네이션.
  async findAll(
    page?: number,
    size?: number,
    opts: { sort?: string; q?: string } = {},
  ): Promise<PaginatedResult<ProjectResponse>> {
    const pg = resolvePage(page, size);
    const q = opts.q?.trim();
    // 정렬: 이름순 / 최근 생성순 / 최근 업데이트순(기본)
    const order: FindOptionsOrder<Project> =
      opts.sort === 'name'
        ? { name: 'ASC' }
        : opts.sort === 'created'
          ? { createdAt: 'DESC' }
          : { updatedAt: 'DESC' };
    const [projects, total] = await this.projectRepo.findAndCount({
      relations: ['members', 'members.user'],
      where: q ? { name: ILike(`%${q}%`) } : {},
      order,
      skip: pg.skip,
      take: pg.take,
    });
    const ids = projects.map((p) => p.id);
    const taskMap = await this.taskCountsByProject(ids);
    return paginated(
      projects.map((p) => this.toResponse(p, taskMap)),
      total,
      pg,
    );
  }

  // 프로젝트 단건 — 인증 사용자면 열람 가능. 현재 사용자 권한(canManage/isOwner) 포함.
  async findOne(projectId: string, userId: string): Promise<ProjectResponse> {
    const project = await this.projectRepo.findOne({
      where: { id: projectId },
      relations: ['members', 'members.user'],
    });
    if (!project) {
      throw new NotFoundException();
    }
    const membership = await this.memberRepo.findOne({
      where: { project: { id: project.id }, user: { id: userId } },
    });
    const taskMap = await this.taskCountsByProject([project.id]);
    const base = this.toResponse(project, taskMap);
    return {
      ...base,
      canManage: membership?.roleType === 'admin',
      isOwner: membership?.isOwner ?? false,
    };
  }

  // 프로젝트 생성 — 생성자를 owner-admin 멤버로 등록
  async create(
    dto: CreateProjectDto,
    userId: string,
  ): Promise<ProjectResponse> {
    const project = await this.projectRepo.save(
      this.projectRepo.create({
        name: dto.name,
        description: dto.description ?? null,
      }),
    );
    const creator = await this.userService.findById(userId);
    if (creator) {
      await this.memberRepo.save(
        this.memberRepo.create({
          project,
          user: creator,
          roleType: 'admin',
          isOwner: true,
        }),
      );
    }
    return this.findOne(project.id, userId);
  }

  // 프로젝트 수정 — admin 권한.
  async update(
    projectId: string,
    userId: string,
    dto: UpdateProjectDto,
  ): Promise<ProjectResponse> {
    const project = await this.getProjectOrThrow(projectId);
    await this.assertProjectAdmin(project.id, userId);
    if (dto.name !== undefined) project.name = dto.name;
    if (dto.description !== undefined) project.description = dto.description;
    await this.projectRepo.save(project);
    return this.findOne(project.id, userId);
  }

  // 프로젝트 삭제 — 소유자 권한.
  async remove(projectId: string, userId: string): Promise<void> {
    const project = await this.getProjectOrThrow(projectId);
    const membership = await this.memberRepo.findOne({
      where: { project: { id: project.id }, user: { id: userId } },
    });
    if (!membership?.isOwner) {
      throw new ForbiddenException();
    }
    await this.projectRepo.remove(project);
  }

  // --- 다른 모듈에서 쓰는 헬퍼 ---

  // projectId 로 프로젝트 로딩, 없으면 404
  async getProjectOrThrow(projectId: string): Promise<Project> {
    const project = await this.projectRepo.findOne({
      where: { id: projectId },
    });
    if (!project) {
      throw new NotFoundException();
    }
    return project;
  }

  // 프로젝트 멤버 확인 — 아니면 403
  async assertProjectMember(projectId: string, userId: string): Promise<void> {
    const membership = await this.memberRepo.findOne({
      where: { project: { id: projectId }, user: { id: userId } },
    });
    if (!membership) {
      throw new ForbiddenException();
    }
  }

  // 프로젝트 admin 확인 — 아니면 403
  async assertProjectAdmin(projectId: string, userId: string): Promise<void> {
    const membership = await this.memberRepo.findOne({
      where: { project: { id: projectId }, user: { id: userId } },
    });
    if (!membership || membership.roleType !== 'admin') {
      throw new ForbiddenException();
    }
  }

  // 프로젝트별 업무 완료/전체 수 — 진행률 집계용
  private async taskCountsByProject(
    ids: string[],
  ): Promise<Map<string, { done: number; total: number }>> {
    const map = new Map<string, { done: number; total: number }>();
    if (ids.length === 0) return map;
    const rows = await this.taskRepo
      .createQueryBuilder('task')
      .select('task.project_id', 'projectId')
      .addSelect('COUNT(*)', 'total')
      .addSelect("COUNT(*) FILTER (WHERE task.status = 'done')", 'done')
      .where('task.project_id IN (:...ids)', { ids })
      .groupBy('task.project_id')
      .getRawMany<{ projectId: string; total: string; done: string }>();
    for (const r of rows) {
      map.set(r.projectId, { done: Number(r.done), total: Number(r.total) });
    }
    return map;
  }

  // 엔티티 → 응답 매핑. 카운트 맵에서 프로젝트별 집계를 주입한다.
  private toResponse(
    project: Project,
    taskMap: Map<string, { done: number; total: number }>,
  ): ProjectResponse {
    const members = project.members ?? [];
    const counts = taskMap.get(project.id);
    return {
      id: project.id,
      name: project.name,
      description: project.description,
      members: members
        .filter((m) => m.user)
        .map((m) => UserService.toPublic(m.user)),
      memberCount: members.length,
      doneCount: counts?.done ?? 0,
      totalCount: counts?.total ?? 0,
      updatedAt: project.updatedAt.toISOString(),
    };
  }
}
