import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { AuditAction, AuditEntityType } from '@aechr/shared';
import { AssignmentStatus, CampaignStatus, Prisma, SubmissionFinalStatus, UserStatus } from '@prisma/client';
import type { JwtAuthUser } from '../../common/interfaces/jwt-auth-user.interface';
import { buildPagination, buildSoftDeleteWhere } from '../../common/prisma/pagination';
import { AuditService } from '../audit/audit.service';
import { PrismaService } from '../prisma/prisma.service';
import { AssignmentQueryDto } from './dto/assignment-query.dto';
import { BulkAssignmentsDto } from './dto/bulk-assignments.dto';
import { ReopenAssignmentDto } from './dto/reopen-assignment.dto';

@Injectable()
export class AssignmentsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly auditService: AuditService,
  ) {}

  async list(query: AssignmentQueryDto) {
    const { skip, take, meta } = buildPagination(query);
    const where: Prisma.AssessmentAssignmentWhereInput = {
      ...buildSoftDeleteWhere(query.includeDeleted),
      ...(query.campaignId ? { campaignId: query.campaignId } : {}),
      ...(query.employeeId ? { employeeId: query.employeeId } : {}),
      ...(query.status ? { status: query.status } : {}),
      ...(query.isEnabled !== undefined ? { isEnabled: query.isEnabled } : {}),
    };

    const [items, total] = await this.prisma.$transaction([
      this.prisma.assessmentAssignment.findMany({
        where,
        skip,
        take,
        include: {
          campaign: true,
          employee: true,
          submissions: true,
        },
        orderBy: { createdAt: query.sortOrder },
      }),
      this.prisma.assessmentAssignment.count({ where }),
    ]);
    return { items, total, ...meta };
  }

  async findOne(id: string) {
    const assignment = await this.prisma.assessmentAssignment.findUnique({
      where: { id },
      include: {
        campaign: {
          include: {
            template: {
              include: {
                questions: {
                  where: { deletedAt: null },
                  include: { question: true },
                  orderBy: { sortOrder: 'asc' },
                },
              },
            },
          },
        },
        employee: true,
        submissions: { include: { answers: true } },
      },
    });
    if (!assignment) throw new NotFoundException('Assignment not found');
    return assignment;
  }

  async mine(user: JwtAuthUser) {
    return this.prisma.assessmentAssignment.findMany({
      where: {
        employeeId: user.sub,
        deletedAt: null,
      },
      include: { campaign: true, submissions: true },
      orderBy: { createdAt: 'desc' },
    });
  }

  async bulkAssign(dto: BulkAssignmentsDto, actor: JwtAuthUser) {
    const campaign = await this.prisma.assessmentCampaign.findFirst({
      where: { id: dto.campaignId, deletedAt: null },
    });
    if (!campaign) throw new NotFoundException('Campaign not found');
    if (campaign.status !== CampaignStatus.ACTIVE && campaign.status !== CampaignStatus.DRAFT) {
      throw new ConflictException('Assignments can only be created for active or draft campaigns');
    }

    const employees = await this.prisma.user.findMany({
      where: {
        id: { in: dto.employeeIds },
        deletedAt: null,
        status: UserStatus.ACTIVE,
      },
    });
    if (employees.length !== dto.employeeIds.length) {
      throw new NotFoundException('One or more employees were not found or inactive');
    }

    const results = await this.prisma.$transaction(async (tx) => {
      const created = [];
      for (const employeeId of dto.employeeIds) {
        const existing = await tx.assessmentAssignment.findFirst({
          where: { campaignId: dto.campaignId, employeeId },
        });
        if (existing && !existing.deletedAt) {
          created.push(existing);
          continue;
        }
        if (existing && existing.deletedAt) {
          const restored = await tx.assessmentAssignment.update({
            where: { id: existing.id },
            data: {
              deletedAt: null,
              deletedBy: null,
              isEnabled: true,
              enabledBy: actor.sub,
              enabledAt: new Date(),
              status: AssignmentStatus.PENDING,
              assignedBy: actor.sub,
              updatedBy: actor.sub,
            },
          });
          created.push(restored);
          continue;
        }
        created.push(
          await tx.assessmentAssignment.create({
            data: {
              campaignId: dto.campaignId,
              employeeId,
              assignedBy: actor.sub,
              isEnabled: true,
              enabledBy: actor.sub,
              enabledAt: new Date(),
              status: AssignmentStatus.PENDING,
              createdBy: actor.sub,
              updatedBy: actor.sub,
            },
          }),
        );
      }
      return created;
    });

    await this.auditService.log({
      module: 'assignments',
      action: AuditAction.CREATE,
      entityType: AuditEntityType.ASSIGNMENT,
      entityId: dto.campaignId,
      remarks: 'Bulk assignments created',
      newValues: { employeeIds: dto.employeeIds } as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return results;
  }

  async enable(id: string, actor: JwtAuthUser) {
    return this.setEnabled(id, true, actor);
  }

  async disable(id: string, actor: JwtAuthUser) {
    return this.setEnabled(id, false, actor);
  }

  async reopen(id: string, dto: ReopenAssignmentDto, actor: JwtAuthUser) {
    await this.findOne(id);
    const updated = await this.prisma.assessmentAssignment.update({
      where: { id },
      data: {
        status: AssignmentStatus.REOPENED,
        isEnabled: true,
        enabledBy: actor.sub,
        enabledAt: new Date(),
        reopenUntil: dto.reopenUntil ? new Date(dto.reopenUntil) : null,
        updatedBy: actor.sub,
      },
    });
    await this.auditService.log({
      module: 'assignments',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.ASSIGNMENT,
      entityId: id,
      remarks: 'Assignment reopened',
      newValues: { reopenUntil: dto.reopenUntil } as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });
    return updated;
  }

  private async setEnabled(id: string, enabled: boolean, actor: JwtAuthUser) {
    await this.findOne(id);
    const updated = await this.prisma.assessmentAssignment.update({
      where: { id },
      data: {
        isEnabled: enabled,
        enabledBy: enabled ? actor.sub : null,
        enabledAt: enabled ? new Date() : null,
        updatedBy: actor.sub,
      },
    });
    await this.auditService.log({
      module: 'assignments',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.ASSIGNMENT,
      entityId: id,
      remarks: enabled ? 'Assignment enabled' : 'Assignment disabled',
      newValues: { isEnabled: enabled } as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });
    return updated;
  }
}
