import {
  BadRequestException,
  ConflictException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Prisma } from '../generated/prisma/client.js';
import {
  DeviceStatus,
  Role,
  TaskStatus,
  UploadAssetType,
} from '../generated/prisma/enums.js';
import { PrismaService } from '../prisma.service.js';
import type { ActorContext } from '../common/actor-context.js';
import { PublishTaskDto } from './dto/publish-task.dto.js';
import { AcceptTaskDto } from './dto/accept-task.dto.js';
import { CompleteTaskDto } from './dto/complete-task.dto.js';
import { CancelTaskDto } from './dto/cancel-task.dto.js';
import { TaskRecordQueryDto } from './dto/task-record-query.dto.js';
import { MESSAGES } from '../common/messages.js';
import { normalizePressureLabel } from '../common/pressure-level.util.js';

/**
 * 任务服务：封装调压任务状态机、院内隔离与事件发布逻辑。
 */
@Injectable()
export class TaskService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly eventEmitter: EventEmitter2,
  ) {}

  /**
   * 查询当前角色可见的医院工程师列表。
   */
  async findAssignableEngineers(
    actor: ActorContext,
    requestedHospitalId?: number,
  ) {
    this.assertRole(actor, [
      Role.SYSTEM_ADMIN,
      Role.HOSPITAL_ADMIN,
      Role.DOCTOR,
      Role.DIRECTOR,
      Role.LEADER,
    ]);

    const hospitalId = this.resolveAssignableHospitalId(
      actor,
      requestedHospitalId,
    );
    return this.prisma.user.findMany({
      where: {
        role: Role.ENGINEER,
        hospitalId,
      },
      select: {
        id: true,
        name: true,
        phone: true,
        hospitalId: true,
      },
      orderBy: { id: 'desc' },
    });
  }

  /**
   * 查询当前角色可见的调压记录列表。
   */
  async findTaskRecords(actor: ActorContext, query: TaskRecordQueryDto) {
    this.assertRole(actor, [
      Role.SYSTEM_ADMIN,
      Role.HOSPITAL_ADMIN,
      Role.DOCTOR,
      Role.DIRECTOR,
      Role.LEADER,
      Role.ENGINEER,
    ]);

    const hospitalId = this.resolveListHospitalId(actor, query.hospitalId);
    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 20;
    const skip = (page - 1) * pageSize;
    const where = this.buildTaskRecordWhere(actor, query, hospitalId);

    const [total, items] = await Promise.all([
      this.prisma.taskItem.count({ where }),
      this.prisma.taskItem.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { id: 'desc' },
        select: {
          id: true,
          oldPressure: true,
          targetPressure: true,
          task: {
            select: {
              id: true,
              status: true,
              createdAt: true,
              completionMaterials: true,
              hospital: {
                select: {
                  id: true,
                  name: true,
                },
              },
              creator: {
                select: {
                  id: true,
                  name: true,
                  role: true,
                },
              },
              engineer: {
                select: {
                  id: true,
                  name: true,
                  role: true,
                },
              },
            },
          },
          device: {
            select: {
              id: true,
              currentPressure: true,
              implantModel: true,
              implantManufacturer: true,
              implantName: true,
              patient: {
                select: {
                  id: true,
                  name: true,
                  inpatientNo: true,
                  phone: true,
                },
              },
              surgery: {
                select: {
                  id: true,
                  surgeryName: true,
                  surgeryDate: true,
                },
              },
            },
          },
        },
      }),
    ]);

    return {
      list: items.map((item) => ({
        id: item.id,
        oldPressure: item.oldPressure,
        targetPressure: item.targetPressure,
        currentPressure: item.device.currentPressure,
        patientPhone: item.device.patient.phone,
        taskId: item.task.id,
        status: item.task.status,
        createdAt: item.task.createdAt,
        completionMaterials: Array.isArray(item.task.completionMaterials)
          ? item.task.completionMaterials
          : [],
        hospital: item.task.hospital,
        creator: item.task.creator,
        engineer: item.task.engineer,
        patient: item.device.patient,
        surgery: item.device.surgery,
        device: {
          id: item.device.id,
          implantModel: item.device.implantModel,
          implantManufacturer: item.device.implantManufacturer,
          implantName: item.device.implantName,
        },
      })),
      total,
      page,
      pageSize,
    };
  }

  /**
   * 发布任务：管理员或临床角色创建主任务与明细，等待本院工程师接收。
   */
  async publishTask(actor: ActorContext, dto: PublishTaskDto) {
    this.assertRole(actor, [
      Role.SYSTEM_ADMIN,
      Role.HOSPITAL_ADMIN,
      Role.DOCTOR,
      Role.DIRECTOR,
      Role.LEADER,
    ]);

    if (!Array.isArray(dto.items) || dto.items.length === 0) {
      throw new BadRequestException(MESSAGES.TASK.ITEMS_REQUIRED);
    }

    const deviceIds = Array.from(
      new Set(
        dto.items.map((item) => {
          if (!Number.isInteger(item.deviceId)) {
            throw new BadRequestException(`deviceId 非法: ${item.deviceId}`);
          }
          return item.deviceId;
        }),
      ),
    );

    const scopedHospitalId = this.resolveScopedHospitalId(actor);
    const devices = await this.prisma.device.findMany({
      where: {
        id: { in: deviceIds },
        status: DeviceStatus.ACTIVE,
        isAbandoned: false,
        isPressureAdjustable: true,
        patient: scopedHospitalId
          ? {
              hospitalId: scopedHospitalId,
            }
          : undefined,
      },
      select: {
        id: true,
        currentPressure: true,
        patient: {
          select: {
            hospitalId: true,
          },
        },
        implantCatalog: {
          select: {
            pressureLevels: true,
          },
        },
      },
    });

    if (devices.length !== deviceIds.length) {
      throw new NotFoundException(MESSAGES.TASK.DEVICE_NOT_FOUND);
    }

    const hospitalId = this.resolveTaskHospitalId(
      actor,
      devices.map((device) => device.patient.hospitalId),
    );
    await this.assertNoDuplicateOpenTaskForDevices(deviceIds);

    const pressureByDeviceId = new Map(
      devices.map((device) => [device.id, device.currentPressure] as const),
    );
    const pressureLevelsByDeviceId = new Map(
      devices.map((device) => [
        device.id,
        Array.isArray(device.implantCatalog?.pressureLevels)
          ? device.implantCatalog.pressureLevels
          : [],
      ]),
    );

    dto.items.forEach((item) => {
      const normalizedTargetPressure = this.normalizeTargetPressure(
        item.targetPressure,
      );
      const pressureLevels = pressureLevelsByDeviceId.get(item.deviceId) ?? [];
      if (
        pressureLevels.length > 0 &&
        !pressureLevels.includes(normalizedTargetPressure)
      ) {
        throw new BadRequestException(MESSAGES.DEVICE.PRESSURE_LEVEL_INVALID);
      }

      item.targetPressure = normalizedTargetPressure;
    });

    const task = await this.prisma.task.create({
      data: {
        status: TaskStatus.PENDING,
        creatorId: actor.id,
        hospitalId,
        items: {
          create: dto.items.map((item) => ({
            deviceId: item.deviceId,
            oldPressure: pressureByDeviceId.get(item.deviceId) ?? '0',
            targetPressure: item.targetPressure,
          })),
        },
      },
      include: { items: true },
    });

    await this.eventEmitter.emitAsync('task.published', {
      taskId: task.id,
      hospitalId: task.hospitalId,
      actorId: actor.id,
      status: task.status,
    });

    return task;
  }

  /**
   * 接收任务：工程师接收本院待处理任务，任务一旦被接收不可重复抢单。
   */
  async acceptTask(actor: ActorContext, dto: AcceptTaskDto) {
    this.assertRole(actor, [Role.ENGINEER]);
    const hospitalId = this.requireHospitalId(actor);

    const task = await this.prisma.task.findFirst({
      where: {
        id: dto.taskId,
        hospitalId,
      },
      select: {
        id: true,
        status: true,
        engineerId: true,
      },
    });

    if (!task) {
      throw new NotFoundException(MESSAGES.TASK.TASK_NOT_FOUND);
    }
    if (task.status !== TaskStatus.PENDING) {
      if (task.engineerId && task.engineerId !== actor.id) {
        throw new ConflictException(MESSAGES.TASK.ENGINEER_ALREADY_ASSIGNED);
      }
      throw new ConflictException(MESSAGES.TASK.ACCEPT_ONLY_PENDING);
    }

    const claimResult = await this.prisma.task.updateMany({
      where: {
        id: task.id,
        hospitalId,
        status: TaskStatus.PENDING,
        engineerId: null,
      },
      data: {
        status: TaskStatus.ACCEPTED,
        engineerId: actor.id,
      },
    });

    if (claimResult.count !== 1) {
      const latestTask = await this.prisma.task.findUnique({
        where: { id: task.id },
        select: {
          status: true,
          engineerId: true,
        },
      });
      if (latestTask?.engineerId && latestTask.engineerId !== actor.id) {
        throw new ConflictException(MESSAGES.TASK.ENGINEER_ALREADY_ASSIGNED);
      }
      throw new ConflictException(MESSAGES.TASK.ACCEPT_ONLY_PENDING);
    }

    const acceptedTask = await this.prisma.task.findUnique({
      where: { id: task.id },
      include: { items: true },
    });
    if (!acceptedTask) {
      throw new NotFoundException(MESSAGES.TASK.TASK_NOT_FOUND);
    }

    await this.eventEmitter.emitAsync('task.accepted', {
      taskId: acceptedTask.id,
      hospitalId: acceptedTask.hospitalId,
      actorId: actor.id,
      status: acceptedTask.status,
    });

    return acceptedTask;
  }

  /**
   * 完成任务：工程师将任务置为 COMPLETED，并同步设备当前压力。
   */
  async completeTask(actor: ActorContext, dto: CompleteTaskDto) {
    this.assertRole(actor, [Role.ENGINEER]);
    const hospitalId = this.requireHospitalId(actor);
    const completionMaterials = await this.normalizeCompletionMaterials(
      hospitalId,
      dto.completionMaterials,
    );

    const task = await this.prisma.task.findFirst({
      where: {
        id: dto.taskId,
        hospitalId,
      },
      include: {
        items: true,
      },
    });

    if (!task) {
      throw new NotFoundException(MESSAGES.TASK.TASK_NOT_FOUND);
    }
    if (task.status !== TaskStatus.ACCEPTED) {
      throw new ConflictException(MESSAGES.TASK.COMPLETE_ONLY_ACCEPTED);
    }
    if (task.engineerId !== actor.id) {
      throw new ForbiddenException(MESSAGES.TASK.ENGINEER_ONLY_ASSIGNEE);
    }

    const completedTask = await this.prisma.$transaction(async (tx) => {
      const nextTask = await tx.task.update({
        where: { id: task.id },
        data: {
          status: TaskStatus.COMPLETED,
          completionMaterials,
        },
        include: { items: true },
      });

      await Promise.all(
        task.items.map((item) =>
          tx.device.update({
            where: { id: item.deviceId },
            data: { currentPressure: item.targetPressure },
          }),
        ),
      );

      return nextTask;
    });

    await this.eventEmitter.emitAsync('task.completed', {
      taskId: completedTask.id,
      hospitalId: completedTask.hospitalId,
      actorId: actor.id,
      status: completedTask.status,
    });

    return completedTask;
  }

  /**
   * 取消任务：
   * 1. 创建者可将 PENDING/ACCEPTED 任务真正取消为 CANCELLED；
   * 2. 接收工程师可将自己已接收任务退回为 PENDING，供其他工程师重新接收。
   */
  async cancelTask(actor: ActorContext, dto: CancelTaskDto) {
    this.assertRole(actor, [
      Role.SYSTEM_ADMIN,
      Role.HOSPITAL_ADMIN,
      Role.DOCTOR,
      Role.DIRECTOR,
      Role.LEADER,
      Role.ENGINEER,
    ]);
    const scopedHospitalId = this.resolveScopedHospitalId(actor);

    const task = await this.prisma.task.findFirst({
      where: {
        id: dto.taskId,
        hospitalId: scopedHospitalId ?? undefined,
      },
      select: {
        id: true,
        status: true,
        creatorId: true,
        engineerId: true,
        hospitalId: true,
      },
    });

    if (!task) {
      throw new NotFoundException(MESSAGES.TASK.TASK_NOT_FOUND);
    }
    if (actor.role === Role.ENGINEER) {
      if (task.engineerId !== actor.id) {
        throw new ForbiddenException(MESSAGES.TASK.CANCEL_ONLY_ASSIGNEE);
      }
      if (task.status !== TaskStatus.ACCEPTED) {
        throw new ConflictException(MESSAGES.TASK.CANCEL_ONLY_PENDING_ACCEPTED);
      }

      const releasedTask = await this.prisma.task.update({
        where: { id: task.id },
        data: {
          status: TaskStatus.PENDING,
          engineerId: null,
        },
        include: { items: true },
      });

      await this.eventEmitter.emitAsync('task.released', {
        taskId: releasedTask.id,
        hospitalId: releasedTask.hospitalId,
        actorId: actor.id,
        status: releasedTask.status,
        reason: dto.reason?.trim() || null,
      });

      return releasedTask;
    } else if (task.creatorId !== actor.id) {
      throw new ForbiddenException(MESSAGES.TASK.CANCEL_ONLY_CREATOR);
    }
    if (
      task.status !== TaskStatus.PENDING &&
      task.status !== TaskStatus.ACCEPTED
    ) {
      throw new ConflictException(MESSAGES.TASK.CANCEL_ONLY_PENDING_ACCEPTED);
    }

    const cancelledTask = await this.prisma.task.update({
      where: { id: task.id },
      data: { status: TaskStatus.CANCELLED },
      include: { items: true },
    });

    await this.eventEmitter.emitAsync('task.cancelled', {
      taskId: cancelledTask.id,
      hospitalId: cancelledTask.hospitalId,
      actorId: actor.id,
      status: cancelledTask.status,
      // 当前库表未持久化取消原因，但先透传到事件层，方便通知链路后续接入。
      reason: dto.reason?.trim() || null,
    });

    return cancelledTask;
  }

  /**
   * 校验角色权限。
   */
  private assertRole(actor: ActorContext, allowedRoles: Role[]) {
    if (!allowedRoles.includes(actor.role)) {
      throw new ForbiddenException(MESSAGES.TASK.ACTOR_ROLE_FORBIDDEN);
    }
  }

  /**
   * 返回角色可见的医院范围。系统管理员可不绑定医院，按目标设备自动归院。
   */
  private resolveScopedHospitalId(actor: ActorContext): number | null {
    if (actor.role === Role.SYSTEM_ADMIN) {
      return actor.hospitalId ?? null;
    }
    if (!actor.hospitalId) {
      throw new BadRequestException(MESSAGES.TASK.ACTOR_HOSPITAL_REQUIRED);
    }
    return actor.hospitalId;
  }

  /**
   * 解析本次任务归属医院，确保同一批设备不跨院。
   */
  private resolveTaskHospitalId(
    actor: ActorContext,
    hospitalIds: number[],
  ): number {
    const uniqueHospitalIds = Array.from(
      new Set(hospitalIds.filter((hospitalId) => Number.isInteger(hospitalId))),
    );

    if (uniqueHospitalIds.length !== 1) {
      throw new BadRequestException(MESSAGES.TASK.DEVICE_MULTI_HOSPITAL);
    }

    const [hospitalId] = uniqueHospitalIds;
    if (actor.hospitalId && actor.hospitalId !== hospitalId) {
      throw new ForbiddenException(MESSAGES.TASK.DEVICE_NOT_FOUND);
    }

    return hospitalId;
  }

  /**
   * 解析工程师指派范围。系统管理员可显式指定医院，其余角色固定本院。
   */
  private resolveAssignableHospitalId(
    actor: ActorContext,
    requestedHospitalId?: number,
  ) {
    if (actor.role === Role.SYSTEM_ADMIN) {
      if (requestedHospitalId !== undefined) {
        return requestedHospitalId;
      }

      return this.requireHospitalId(actor);
    }

    return this.requireHospitalId(actor);
  }

  /**
   * 列表查询的医院范围解析。系统管理员可按查询条件切院，其余角色固定本院。
   */
  private resolveListHospitalId(
    actor: ActorContext,
    requestedHospitalId?: number,
  ) {
    if (actor.role === Role.SYSTEM_ADMIN) {
      return requestedHospitalId ?? actor.hospitalId ?? undefined;
    }

    return this.requireHospitalId(actor);
  }

  /**
   * 构造调压记录查询条件。
   */
  private buildTaskRecordWhere(
    actor: ActorContext,
    query: TaskRecordQueryDto,
    hospitalId?: number,
  ): Prisma.TaskItemWhereInput {
    const keyword = query.keyword?.trim();
    const patientScope = this.buildTaskPatientScope(actor);

    const where: Prisma.TaskItemWhereInput = {
      task: {
        hospitalId,
        status: query.status,
      },
      device: patientScope
        ? {
            patient: patientScope,
          }
        : undefined,
    };

    if (!keyword) {
      return where;
    }

    where.OR = [
      {
        device: {
          patient: {
            name: {
              contains: keyword,
              mode: 'insensitive',
            },
          },
        },
      },
      {
        device: {
          patient: {
            inpatientNo: {
              contains: keyword,
              mode: 'insensitive',
            },
          },
        },
      },
      {
        device: {
          patient: {
            phone: {
              contains: keyword,
              mode: 'insensitive',
            },
          },
        },
      },
      {
        device: {
          implantName: {
            contains: keyword,
            mode: 'insensitive',
          },
        },
      },
      {
        device: {
          implantModel: {
            contains: keyword,
            mode: 'insensitive',
          },
        },
      },
    ];

    return where;
  }

  /**
   * 按角色构造任务列表中的患者可见范围，保持与患者列表一致。
   */
  private buildTaskPatientScope(actor: ActorContext): Prisma.PatientWhereInput | null {
    switch (actor.role) {
      case Role.SYSTEM_ADMIN:
      case Role.HOSPITAL_ADMIN:
      case Role.ENGINEER:
        return null;
      case Role.DOCTOR:
        return {
          doctorId: actor.id,
        };
      case Role.LEADER:
        if (!actor.groupId) {
          throw new BadRequestException(MESSAGES.PATIENT.GROUP_REQUIRED);
        }
        return {
          doctor: {
            groupId: actor.groupId,
          },
        };
      case Role.DIRECTOR:
        if (!actor.departmentId) {
          throw new BadRequestException(MESSAGES.PATIENT.DEPARTMENT_REQUIRED);
        }
        return {
          doctor: {
            departmentId: actor.departmentId,
          },
        };
      default:
        throw new ForbiddenException(MESSAGES.TASK.ACTOR_ROLE_FORBIDDEN);
    }
  }

  /**
   * 调压目标挡位标准化。
   */
  private normalizeTargetPressure(value: unknown) {
    return normalizePressureLabel(value, 'targetPressure');
  }

  /**
   * 完成任务凭证标准化：仅允许当前医院下的图片/视频上传资产。
   */
  private async normalizeCompletionMaterials(
    hospitalId: number,
    materials: CompleteTaskDto['completionMaterials'],
  ): Promise<Prisma.InputJsonArray> {
    if (!Array.isArray(materials) || materials.length === 0) {
      throw new BadRequestException(MESSAGES.TASK.COMPLETE_MATERIALS_REQUIRED);
    }

    const assetIds = Array.from(
      new Set(
        materials.map((material) => {
          const assetId = Number(material.assetId);
          if (!Number.isInteger(assetId) || assetId <= 0) {
            throw new BadRequestException('assetId 必须是整数');
          }
          return assetId;
        }),
      ),
    );

    const assets = await this.prisma.uploadAsset.findMany({
      where: {
        id: { in: assetIds },
        hospitalId,
        type: { in: [UploadAssetType.IMAGE, UploadAssetType.VIDEO] },
      },
      select: {
        id: true,
        type: true,
        url: true,
        originalName: true,
      },
    });
    if (assets.length !== assetIds.length) {
      throw new BadRequestException(
        MESSAGES.TASK.COMPLETE_MATERIAL_TYPE_INVALID,
      );
    }

    const assetMap = new Map(assets.map((asset) => [asset.id, asset]));

    return materials.map((material) => {
      const asset = assetMap.get(material.assetId);
      if (
        !asset ||
        (asset.type !== UploadAssetType.IMAGE &&
          asset.type !== UploadAssetType.VIDEO)
      ) {
        throw new BadRequestException(
          MESSAGES.TASK.COMPLETE_MATERIAL_TYPE_INVALID,
        );
      }

      return {
        assetId: asset.id,
        type: asset.type,
        url: asset.url,
        name:
          typeof material.name === 'string' && material.name.trim()
            ? material.name.trim()
            : asset.originalName,
      };
    }) as Prisma.InputJsonArray;
  }

  /**
   * 工程师侧任务流转仍要求明确的院内身份。
   */
  private requireHospitalId(actor: ActorContext): number {
    if (!actor.hospitalId) {
      throw new BadRequestException(MESSAGES.TASK.ACTOR_HOSPITAL_REQUIRED);
    }
    return actor.hospitalId;
  }

  /**
   * 当前设备存在待接收/已接收任务时，禁止重复发布。
   */
  private async assertNoDuplicateOpenTaskForDevices(deviceIds: number[]) {
    if (deviceIds.length === 0) {
      return;
    }

    const duplicateOpenTask = await this.prisma.taskItem.findFirst({
      where: {
        task: {
          status: {
            in: [TaskStatus.PENDING, TaskStatus.ACCEPTED],
          },
        },
        deviceId: {
          in: deviceIds,
        },
      },
      select: { id: true },
    });

    if (duplicateOpenTask) {
      throw new ConflictException(MESSAGES.TASK.DUPLICATE_DEVICE_OPEN_TASK);
    }
  }
}
