import { ForbiddenException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { TaskService } from './task.service';
import { BusinessException } from '../../common/exceptions/business.exception';
import { Project } from '../project/entities/project.entity';
import { ProjectMember } from '../project/entities/project-member.entity';
import { Task } from './entities/task.entity';
import { ChecklistItem } from './entities/checklist-item.entity';
import { Comment } from './entities/comment.entity';
import { Attachment } from './entities/attachment.entity';
import { TaskCheckpoint } from './entities/task-checkpoint.entity';
import { CheckpointReport } from './entities/checkpoint-report.entity';
import { ActivityService } from '../activity/activity.service';
import { NotificationService } from '../notification/notification.service';

// 리포지토리 간이 모킹
function makeService() {
  const taskRepo = {
    findOne: jest.fn(),
    create: jest.fn(),
    save: jest.fn(),
    remove: jest.fn(),
    createQueryBuilder: jest.fn(),
  };
  const checklistRepo = { create: jest.fn(), save: jest.fn() };
  const projectRepo = { findOne: jest.fn() };
  const memberRepo = { findOne: jest.fn(), find: jest.fn() };
  // 상세 조립(buildDetail)에서 댓글/첨부는 빈 목록으로 모킹
  const commentRepo = { find: jest.fn().mockResolvedValue([]) };
  const attachmentRepo = { find: jest.fn().mockResolvedValue([]) };
  // 중간 점검(체크포인트)·보고 — buildDetail 에서 빈 목록으로 모킹
  const checkpointRepo = {
    find: jest.fn().mockResolvedValue([]),
    findOne: jest.fn(),
    save: jest.fn(),
    remove: jest.fn(),
    create: jest.fn(),
  };
  const reportRepo = {
    find: jest.fn().mockResolvedValue([]),
    save: jest.fn(),
    create: jest.fn(),
  };
  // 활동 서비스 — 적재는 no-op, 업무 활동 조회는 빈 목록
  const activity = {
    record: jest.fn().mockResolvedValue(undefined),
    listForTask: jest.fn().mockResolvedValue([]),
  };
  // 알림 서비스 — 적재는 no-op
  const notification = { notify: jest.fn().mockResolvedValue(undefined) };
  const svc = new TaskService(
    taskRepo as unknown as Repository<Task>,
    checklistRepo as unknown as Repository<ChecklistItem>,
    projectRepo as unknown as Repository<Project>,
    memberRepo as unknown as Repository<ProjectMember>,
    commentRepo as unknown as Repository<Comment>,
    attachmentRepo as unknown as Repository<Attachment>,
    checkpointRepo as unknown as Repository<TaskCheckpoint>,
    reportRepo as unknown as Repository<CheckpointReport>,
    activity as unknown as ActivityService,
    notification as unknown as NotificationService,
  );
  return {
    svc,
    taskRepo,
    checklistRepo,
    projectRepo,
    memberRepo,
    commentRepo,
    attachmentRepo,
    activity,
  };
}

const REPO = { id: 'repo-uuid', slugName: 'q3-launch', name: '캠페인' };

describe('TaskService', () => {
  describe('changeStatus — 상태 머신', () => {
    it('허용되지 않는 전이(todo→done)는 BusinessException', async () => {
      const { svc, taskRepo, projectRepo, memberRepo } = makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      memberRepo.findOne.mockResolvedValue({ roleType: 'admin' });
      taskRepo.findOne.mockResolvedValue({
        seq: 1,
        status: 'todo',
        issuer: { id: 'admin' },
        assignees: [],
        checklist: [],
      });

      await expect(
        svc.changeStatus('q3-launch', 'admin', 1, 'done'),
      ).rejects.toBeInstanceOf(BusinessException);
    });

    it('승인(review→done)은 지시자/admin 이 아니면 Forbidden', async () => {
      const { svc, taskRepo, projectRepo, memberRepo } = makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      // 요청자는 일반 멤버(담당자)
      memberRepo.findOne.mockResolvedValue({ roleType: 'member' });
      taskRepo.findOne.mockResolvedValue({
        seq: 9,
        status: 'review',
        issuer: { id: 'someone-else' },
        assignees: [{ id: 'assignee' }],
        checklist: [],
      });

      await expect(
        svc.changeStatus('q3-launch', 'assignee', 9, 'done'),
      ).rejects.toBeInstanceOf(ForbiddenException);
    });

    it('비멤버는 상태 변경 불가(Forbidden)', async () => {
      const { svc, projectRepo, memberRepo } = makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      memberRepo.findOne.mockResolvedValue(null); // 멤버십 없음

      await expect(
        svc.changeStatus('q3-launch', 'outsider', 1, 'prog'),
      ).rejects.toBeInstanceOf(ForbiddenException);
    });

    it('승인(review→done) 시 체크리스트를 모두 완료 처리한다', async () => {
      const { svc, taskRepo, checklistRepo, projectRepo, memberRepo } =
        makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      // 지시자/admin — assertMember 는 user 관계까지 로드(알림 actorName 용)
      memberRepo.findOne.mockResolvedValue({
        roleType: 'admin',
        user: { id: 'admin', name: 'A' },
      });
      const task = {
        seq: 9,
        status: 'review',
        title: '배너',
        content: [],
        issuer: { id: 'admin', email: 'a@b.c', name: 'A', role: null },
        assignees: [],
        checklist: [
          { id: 'c1', text: '가로형', done: true, sortOrder: 0 },
          { id: 'c2', text: '세로형', done: false, sortOrder: 1 },
          { id: 'c3', text: '모바일', done: false, sortOrder: 2 },
        ],
        dueDate: null,
        createdAt: new Date('2026-06-08T00:00:00Z'),
      };
      taskRepo.findOne.mockResolvedValue(task);
      taskRepo.save.mockResolvedValue(task);
      checklistRepo.save.mockResolvedValue(task.checklist);

      const result = await svc.changeStatus('q3-launch', 'admin', 9, 'done');

      expect(checklistRepo.save).toHaveBeenCalledTimes(1);
      expect(task.checklist.every((c) => c.done)).toBe(true);
      expect(result.checklist.every((c) => c.done)).toBe(true);
    });
  });

  describe('승인 워크플로우 (5단계)', () => {
    it('수정 요청(review→changes)은 지시자/admin 이 아니면 Forbidden', async () => {
      const { svc, taskRepo, projectRepo, memberRepo } = makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      memberRepo.findOne.mockResolvedValue({
        roleType: 'member',
        user: { id: 'assignee' },
      });
      taskRepo.findOne.mockResolvedValue({
        seq: 9,
        status: 'review',
        issuer: { id: 'someone-else' },
        assignees: [{ id: 'assignee' }],
        checklist: [],
      });

      await expect(
        svc.requestChanges('q3-launch', 'assignee', 9, '보완 바랍니다'),
      ).rejects.toBeInstanceOf(ForbiddenException);
    });

    it('수정 요청 시 전달된 항목만 완료 체크(체크/X)를 반영한다', async () => {
      const { svc, taskRepo, checklistRepo, projectRepo, memberRepo } =
        makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      memberRepo.findOne.mockResolvedValue({
        roleType: 'admin',
        user: { id: 'admin', name: 'A' },
      });
      const task = {
        seq: 9,
        status: 'review',
        title: '배너',
        content: [],
        issuer: { id: 'admin', email: 'a@b.c', name: 'A', role: null },
        assignees: [],
        checklist: [
          { id: 'c1', text: '가로형', done: true, sortOrder: 0 },
          { id: 'c2', text: '세로형', done: false, sortOrder: 1 },
          { id: 'c3', text: '모바일', done: true, sortOrder: 2 },
        ],
        dueDate: null,
        createdAt: new Date('2026-06-08T00:00:00Z'),
      };
      taskRepo.findOne.mockResolvedValue(task);
      taskRepo.save.mockResolvedValue(task);
      checklistRepo.save.mockResolvedValue(task.checklist);

      const result = await svc.requestChanges(
        'q3-launch',
        'admin',
        9,
        '보완 바랍니다',
        [
          { id: 'c1', done: false, note: '여백을 더 주세요' }, // 완성 → 미완성(X) + 보완 내용
          { id: 'c2', done: true, note: '무시됨' }, // 미완성 → 완성(체크): 보완 내용 비움
          // c3 은 미전달 → 기존값(true) 유지
        ],
      );

      expect(checklistRepo.save).toHaveBeenCalledTimes(1);
      const c1 = task.checklist.find((c) => c.id === 'c1');
      const c2 = task.checklist.find((c) => c.id === 'c2');
      const c3 = task.checklist.find((c) => c.id === 'c3');
      expect(c1?.done).toBe(false);
      expect(c1?.reviewNote).toBe('여백을 더 주세요'); // 미완성 항목은 보완 내용 보관
      expect(c2?.done).toBe(true);
      expect(c2?.reviewNote).toBeNull(); // 완료 항목은 보완 내용 비움
      expect(c3?.done).toBe(true);
      expect(result.status).toBe('changes');
      expect(result.changesNote).toBe('보완 바랍니다');
    });

    it('승인 요청(prog→review)은 담당자가 가능', async () => {
      const {
        svc,
        taskRepo,
        projectRepo,
        memberRepo,
        commentRepo,
        attachmentRepo,
      } = makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      memberRepo.findOne.mockResolvedValue({
        roleType: 'member',
        user: { id: 'assignee', email: 'a@b.c', name: 'A', role: null },
      });
      const task = {
        seq: 9,
        status: 'prog',
        title: '배너',
        content: [],
        issuer: { id: 'boss' },
        assignees: [{ id: 'assignee' }],
        checklist: [],
        dueDate: null,
        createdAt: new Date('2026-06-08T00:00:00Z'),
        approvalRequestedBy: {
          id: 'assignee',
          email: 'a@b.c',
          name: 'A',
          role: null,
        },
        approvalRequestedAt: new Date('2026-06-10T00:00:00Z'),
        approvalNote: '검토 부탁드립니다',
      };
      taskRepo.findOne.mockResolvedValue(task);
      taskRepo.save.mockResolvedValue(task);
      commentRepo.find.mockResolvedValue([]);
      attachmentRepo.find.mockResolvedValue([]);

      const result = await svc.requestApproval(
        'q3-launch',
        'assignee',
        9,
        '검토 부탁드립니다',
      );

      expect(task.status).toBe('review');
      expect(result.approval?.note).toBe('검토 부탁드립니다');
    });
  });

  describe('create — 담당자 멤버 검증', () => {
    it('담당자가 프로젝트 멤버가 아니면 BusinessException', async () => {
      const { svc, projectRepo, memberRepo } = makeService();
      projectRepo.findOne.mockResolvedValue(REPO);
      // assertAdmin 통과
      memberRepo.findOne.mockResolvedValue({
        roleType: 'admin',
        user: { id: 'admin' },
      });
      // 요청한 담당자 2명 중 매칭 멤버십 1건만 → 비멤버 포함
      memberRepo.find.mockResolvedValue([{ user: { id: 'u1' } }]);

      await expect(
        svc.create('q3-launch', 'admin', {
          title: '배너 디자인',
          assigneeIds: ['u1', 'u2'],
        }),
      ).rejects.toBeInstanceOf(BusinessException);
    });
  });
});
