import { ConflictException, ForbiddenException } from '@nestjs/common';
import {
  AssignmentStatus,
  QuestionFieldType,
  SubmissionFinalStatus,
} from '@prisma/client';
import { describe, expect, it, vi } from 'vitest';
import { SubmissionsService } from '../src/modules/submissions/submissions.service';

const user = {
  sub: 'user-1',
  email: 'admin@aechr.local',
  employeeCode: 'EMP-1',
  name: 'Admin',
  roleId: 'role-1',
  departmentId: 'dept-1',
  designation: 'HR',
  permissions: [],
};

function createAssignment(overrides?: Record<string, unknown>) {
  return {
    id: 'assignment-1',
    employeeId: 'user-1',
    isEnabled: true,
    status: AssignmentStatus.PENDING,
    deletedAt: null,
    campaign: {
      template: {
        questions: [
          {
            id: 'mapping-1',
            questionId: 'question-1',
            isRequiredOverride: true,
            question: {
              id: 'question-1',
              label: 'Department rating',
              isRequired: true,
              fieldType: QuestionFieldType.SELECT,
              optionsJson: ['A', 'B', 'C'],
            },
          },
        ],
      },
    },
    submissions: [],
    ...overrides,
  };
}

describe('SubmissionsService', () => {
  it('rejects submission for another employee assignment', async () => {
    const prisma = {
      assessmentAssignment: {
        findUnique: vi.fn().mockResolvedValue(createAssignment({ employeeId: 'other-user' })),
      },
    } as never;
    const service = new SubmissionsService(prisma, { log: vi.fn() } as never);

    await expect(
      service.saveDraft('assignment-1', { answers: [] }, user),
    ).rejects.toBeInstanceOf(ForbiddenException);
  });

  it('rejects final submission when a required answer is missing', async () => {
    const prisma = {
      assessmentAssignment: {
        findUnique: vi.fn().mockResolvedValue(createAssignment()),
      },
    } as never;
    const service = new SubmissionsService(prisma, { log: vi.fn() } as never);

    await expect(
      service.submit('assignment-1', { answers: [] }, user),
    ).rejects.toBeInstanceOf(ConflictException);
  });

  it('rejects invalid selectable option values', async () => {
    const prisma = {
      assessmentAssignment: {
        findUnique: vi.fn().mockResolvedValue(createAssignment()),
      },
    } as never;
    const service = new SubmissionsService(prisma, { log: vi.fn() } as never);

    await expect(
      service.submit(
        'assignment-1',
        { answers: [{ questionId: 'question-1', answerJson: 'Z' }] },
        user,
      ),
    ).rejects.toBeInstanceOf(ConflictException);
  });

  it('returns existing non-deleted answers when reopening the form', async () => {
    const prisma = {
      assessmentAssignment: {
        findUnique: vi.fn().mockResolvedValue(
          createAssignment({
            campaign: {
              periodMonth: 3,
              periodYear: 2026,
              startDate: new Date('2026-03-01T00:00:00.000Z'),
              template: {
                questions: [
                  {
                    id: 'mapping-1',
                    questionId: 'question-1',
                    isRequiredOverride: true,
                    question: {
                      id: 'question-1',
                      code: 'QUESTION_1',
                      label: 'Department rating',
                      isRequired: true,
                      fieldType: QuestionFieldType.SELECT,
                      optionsJson: ['A', 'B', 'C'],
                    },
                  },
                ],
              },
            },
            employee: {
              designation: 'HR',
              manager: { name: 'Manager' },
            },
            submissions: [
              {
                id: 'submission-1',
                finalStatus: SubmissionFinalStatus.DRAFT,
                answers: [
                  {
                    id: 'answer-1',
                    questionId: 'question-1',
                    answerJson: 'A',
                    answerText: null,
                  },
                ],
              },
            ],
          }),
        ),
      },
    } as never;
    const service = new SubmissionsService(prisma, { log: vi.fn() } as never);

    const result = await service.getAssignmentForm('assignment-1', user);

    expect(result.submission?.answers).toEqual([
      expect.objectContaining({
        questionId: 'question-1',
        answerJson: 'A',
      }),
    ]);
  });

  it('persists a valid final submission and updates assignment status', async () => {
    const prisma = {
      assessmentAssignment: {
        findUnique: vi.fn().mockResolvedValue(createAssignment()),
        update: vi.fn().mockResolvedValue(undefined),
      },
      assessmentSubmission: {
        create: vi.fn().mockResolvedValue({ id: 'submission-1' }),
        findUnique: vi.fn().mockResolvedValue({ id: 'submission-1', answers: [] }),
      },
      assessmentAnswer: {
        updateMany: vi.fn().mockResolvedValue(undefined),
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn().mockResolvedValue(undefined),
      },
      $transaction: vi.fn().mockResolvedValue(undefined),
    } as never;
    const audit = { log: vi.fn().mockResolvedValue(undefined) } as never;
    const service = new SubmissionsService(prisma, audit);

    const result = await service.submit(
      'assignment-1',
      { answers: [{ questionId: 'question-1', answerJson: 'A' }] },
      user,
    );

    expect(prisma.assessmentSubmission.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        finalStatus: SubmissionFinalStatus.SUBMITTED,
      }),
    });
    expect(prisma.assessmentAssignment.update).toHaveBeenCalledWith({
      where: { id: 'assignment-1' },
      data: expect.objectContaining({
        status: AssignmentStatus.SUBMITTED,
      }),
    });
    expect(result).toEqual({ id: 'submission-1', answers: [] });
  });

  it('clears prior submitted metadata when a reopened submission is saved as draft', async () => {
    const existingSubmittedAt = new Date('2026-03-10T09:00:00.000Z');
    const prisma = {
      assessmentAssignment: {
        findUnique: vi.fn().mockResolvedValue(
          createAssignment({
            status: AssignmentStatus.REOPENED,
            submissions: [
              {
                id: 'submission-1',
                submittedBy: 'user-1',
                submittedAt: existingSubmittedAt,
                finalStatus: SubmissionFinalStatus.SUBMITTED,
                answers: [],
              },
            ],
          }),
        ),
        update: vi.fn().mockResolvedValue(undefined),
      },
      assessmentSubmission: {
        update: vi.fn().mockResolvedValue({ id: 'submission-1' }),
        findUnique: vi.fn().mockResolvedValue({ id: 'submission-1', answers: [], finalStatus: 'DRAFT' }),
      },
      assessmentAnswer: {
        updateMany: vi.fn().mockResolvedValue(undefined),
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn().mockResolvedValue(undefined),
      },
      $transaction: vi.fn().mockResolvedValue(undefined),
    } as never;
    const audit = { log: vi.fn().mockResolvedValue(undefined) } as never;
    const service = new SubmissionsService(prisma, audit);

    await service.saveDraft('assignment-1', { answers: [] }, user);

    expect(prisma.assessmentSubmission.update).toHaveBeenCalledWith({
      where: { id: 'submission-1' },
      data: expect.objectContaining({
        finalStatus: SubmissionFinalStatus.DRAFT,
        submittedBy: null,
        submittedAt: null,
      }),
    });
    expect(prisma.assessmentAssignment.update).toHaveBeenCalledWith({
      where: { id: 'assignment-1' },
      data: expect.objectContaining({
        status: AssignmentStatus.DRAFT,
      }),
    });
  });
});
