import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { Prisma } from '@prisma/client';
import type { Request } from 'express';
import { MeetingAttendanceError } from '../errors/meeting-attendance.error';
import {
  agendaItemNotFound,
  agendaSectionNotFound,
} from '../errors/agenda.error';
import { MeetingAttendanceAuditLogWriter } from './audit-log-writer.service';
import {
  MEETING_ATTENDANCE_AUDIT_ACTIONS,
  MEETING_ATTENDANCE_AUDIT_RESOURCES,
} from '../constants/audit';
import {
  CreateAgendaItemDto,
  CreateAgendaSectionDto,
  UpdateAgendaItemDto,
  UpdateAgendaSectionDto,
} from '../dto/agenda.dto';

/**
 * 议程能力 v1.0：段（Section）+ 项（Item）的 CRUD + 软删 cascade + reorder。
 *
 * 关键不变量：
 * - 所有 list/find 默认 `deletedAt: null` 过滤
 * - 删除是软删（`UPDATE deletedAt = now()`），段/项软删时同事务 cascade 软删下属
 * - reorder 用单事务批量 `UPDATE ... SET order = ...`
 * - audit 写入：成功路径 commit 后写、失败路径在 controller 兜底；避免 in-tx audit 撞 FK
 */
@Injectable()
export class AgendaService {
  private readonly logger = new Logger(AgendaService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly auditLogWriter: MeetingAttendanceAuditLogWriter,
  ) {}

  // ---------- 议程查看（GET /meetings/:id/agenda） ----------

  async getAgendaTree(meetingId: string) {
    const meeting = await this.prisma.meeting.findUnique({
      where: { id: meetingId },
      select: { id: true },
    });
    if (!meeting) {
      throw new MeetingAttendanceError(404, 'Meeting not found', 'MEETING_ATTENDANCE_003');
    }

    const sections = await this.prisma.meetingAgendaSection.findMany({
      where: { meetingId, deletedAt: null },
      orderBy: { order: 'asc' },
      include: {
        items: {
          where: { deletedAt: null },
          orderBy: { order: 'asc' },
          include: {
            presenter: {
              select: { id: true, displayName: true, email: true },
            },
            uploadTasks: {
              where: { deletedAt: null },
              include: {
                assignee: { select: { id: true, displayName: true, email: true } },
                assignedBy: { select: { id: true, displayName: true, email: true } },
              },
            },
            attachments: {
              where: { deletedAt: null },
              orderBy: { uploadedAt: 'desc' },
              include: {
                uploadedBy: { select: { id: true, displayName: true, email: true } },
              },
            },
          },
        },
      },
    });

    return { sections };
  }

  // ---------- 议程段 CRUD ----------

  async createSection(
    meetingId: string,
    dto: CreateAgendaSectionDto,
    actor: { userId: string; organizationId?: string | null },
    req?: Request,
  ) {
    const meeting = await this.prisma.meeting.findUnique({
      where: { id: meetingId },
      select: { id: true },
    });
    if (!meeting) {
      throw new MeetingAttendanceError(404, 'Meeting not found', 'MEETING_ATTENDANCE_003');
    }

    // 缺省 order = 当前最大 + 1
    let order = dto.order;
    if (order === undefined) {
      const max = await this.prisma.meetingAgendaSection.aggregate({
        where: { meetingId, deletedAt: null },
        _max: { order: true },
      });
      order = (max._max.order ?? -1) + 1;
    }

    const section = await this.prisma.meetingAgendaSection.create({
      data: {
        meetingId,
        order,
        title: dto.title,
        columnLabels: dto.columnLabels ?? [],
        createdById: actor.userId,
        organizationId: actor.organizationId ?? null,
      },
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_SECTION_CREATED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_SECTION,
      statusCode: 201,
      resourceId: section.id,
      changes: { entityId: section.id, entityType: 'AGENDA_SECTION', after: section },
    });

    return { section };
  }

  async updateSection(
    meetingId: string,
    sectionId: string,
    dto: UpdateAgendaSectionDto,
    actor: { userId: string },
    req?: Request,
  ) {
    const existing = await this.prisma.meetingAgendaSection.findFirst({
      where: { id: sectionId, meetingId, deletedAt: null },
    });
    if (!existing) throw agendaSectionNotFound();

    const data: Prisma.MeetingAgendaSectionUpdateInput = {};
    if (dto.title !== undefined) data.title = dto.title;
    if (dto.order !== undefined) data.order = dto.order;
    if (dto.columnLabels !== undefined) data.columnLabels = dto.columnLabels;

    const section = await this.prisma.meetingAgendaSection.update({
      where: { id: sectionId },
      data,
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_SECTION_UPDATED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_SECTION,
      statusCode: 200,
      resourceId: section.id,
      changes: {
        entityId: section.id,
        entityType: 'AGENDA_SECTION',
        before: existing,
        after: section,
      },
    });

    return { section };
  }

  /**
   * 软删议程段 + 同事务 cascade 软删段下所有 item / task / attachment。
   *
   * 用 SELECT FOR UPDATE 锁段行避免并发 INSERT item / attachment 时数据漂移。
   */
  async deleteSection(
    meetingId: string,
    sectionId: string,
    actor: { userId: string },
    req?: Request,
  ): Promise<void> {
    const existing = await this.prisma.meetingAgendaSection.findFirst({
      where: { id: sectionId, meetingId, deletedAt: null },
    });
    if (!existing) throw agendaSectionNotFound();

    await this.prisma.$transaction(async (tx) => {
      // 锁段行
      await tx.$executeRaw`SELECT id FROM platform_meeting_attendance.meeting_agenda_sections WHERE id = ${sectionId}::uuid FOR UPDATE`;

      const items = await tx.meetingAgendaItem.findMany({
        where: { sectionId, deletedAt: null },
        select: { id: true },
      });
      const itemIds = items.map((i) => i.id);

      const now = new Date();

      // cascade soft-delete items
      if (itemIds.length) {
        await tx.meetingAgendaItem.updateMany({
          where: { id: { in: itemIds } },
          data: { deletedAt: now },
        });
        await tx.meetingAgendaItemUploadTask.updateMany({
          where: { agendaItemId: { in: itemIds }, deletedAt: null },
          data: { deletedAt: now },
        });
        await tx.meetingAgendaItemAttachment.updateMany({
          where: { agendaItemId: { in: itemIds }, deletedAt: null },
          data: { deletedAt: now },
        });
      }

      await tx.meetingAgendaSection.update({
        where: { id: sectionId },
        data: { deletedAt: now },
      });
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_SECTION_DELETED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_SECTION,
      statusCode: 204,
      resourceId: sectionId,
      changes: { entityId: sectionId, entityType: 'AGENDA_SECTION', before: existing },
    });
  }

  /**
   * 整批重排段顺序：传入的 ids 必须等于当前会议下全部 section id 集合（缺漏 400）。
   * 单事务批量 update（CASE WHEN）一条 SQL 完成。
   */
  async reorderSections(
    meetingId: string,
    ids: string[],
    actor: { userId: string },
    req?: Request,
  ) {
    const meeting = await this.prisma.meeting.findUnique({
      where: { id: meetingId },
      select: { id: true },
    });
    if (!meeting) {
      throw new MeetingAttendanceError(404, 'Meeting not found', 'MEETING_ATTENDANCE_003');
    }

    const existing = await this.prisma.meetingAgendaSection.findMany({
      where: { meetingId, deletedAt: null },
      select: { id: true },
    });
    const existingIds = new Set(existing.map((s) => s.id));
    const idsSet = new Set(ids);
    if (existingIds.size !== idsSet.size || ![...existingIds].every((id) => idsSet.has(id))) {
      throw new MeetingAttendanceError(
        400,
        'ids must be a complete set of current sections',
        'MEETING_ATTENDANCE_006',
      );
    }

    await this.prisma.$transaction(
      ids.map((id, idx) =>
        this.prisma.meetingAgendaSection.update({
          where: { id },
          data: { order: idx },
        }),
      ),
    );

    const sections = await this.prisma.meetingAgendaSection.findMany({
      where: { meetingId, deletedAt: null },
      orderBy: { order: 'asc' },
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_SECTION_UPDATED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_SECTION,
      statusCode: 200,
      resourceId: meetingId,
      changes: { entityId: meetingId, entityType: 'AGENDA_SECTION_REORDER', after: { ids } },
    });

    return { meetingId, sections };
  }

  // ---------- 议程项 CRUD ----------

  async createItem(
    sectionId: string,
    dto: CreateAgendaItemDto,
    actor: { userId: string; organizationId?: string | null },
    req?: Request,
  ) {
    const section = await this.prisma.meetingAgendaSection.findFirst({
      where: { id: sectionId, deletedAt: null },
      select: { id: true, meetingId: true },
    });
    if (!section) throw agendaSectionNotFound();

    if (dto.presenterUserId) {
      const presenter = await this.prisma.user.findUnique({
        where: { id: dto.presenterUserId },
        select: { id: true },
      });
      if (!presenter) {
        throw new MeetingAttendanceError(
          404,
          'Presenter user not found',
          'MEETING_ATTENDANCE_005',
        );
      }
    }

    let order = dto.order;
    if (order === undefined) {
      const max = await this.prisma.meetingAgendaItem.aggregate({
        where: { sectionId, deletedAt: null },
        _max: { order: true },
      });
      order = (max._max.order ?? -1) + 1;
    }

    const item = await this.prisma.meetingAgendaItem.create({
      data: {
        sectionId,
        order,
        title: dto.title,
        description: dto.description ?? null,
        columnDescriptions: dto.columnDescriptions ?? [],
        code: dto.code ?? null,
        timeMinutes: dto.timeMinutes ?? null,
        presenterUserId: dto.presenterUserId ?? null,
        createdById: actor.userId,
        organizationId: actor.organizationId ?? null,
      },
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_ITEM_CREATED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_ITEM,
      statusCode: 201,
      resourceId: item.id,
      changes: { entityId: item.id, entityType: 'AGENDA_ITEM', after: item },
    });

    return { item };
  }

  async updateItem(
    sectionId: string,
    itemId: string,
    dto: UpdateAgendaItemDto,
    actor: { userId: string },
    req?: Request,
  ) {
    const existing = await this.prisma.meetingAgendaItem.findFirst({
      where: { id: itemId, sectionId, deletedAt: null },
    });
    if (!existing) throw agendaItemNotFound();

    if (dto.presenterUserId) {
      const presenter = await this.prisma.user.findUnique({
        where: { id: dto.presenterUserId },
        select: { id: true },
      });
      if (!presenter) {
        throw new MeetingAttendanceError(
          404,
          'Presenter user not found',
          'MEETING_ATTENDANCE_005',
        );
      }
    }

    const data: Prisma.MeetingAgendaItemUpdateInput = {};
    if (dto.title !== undefined) data.title = dto.title;
    if (dto.description !== undefined) data.description = dto.description ?? null;
    if (dto.code !== undefined) data.code = dto.code ?? null;
    if (dto.timeMinutes !== undefined) data.timeMinutes = dto.timeMinutes ?? null;
    if (dto.presenterUserId !== undefined) {
      data.presenter = dto.presenterUserId
        ? { connect: { id: dto.presenterUserId } }
        : { disconnect: true };
    }
    if (dto.columnDescriptions !== undefined) data.columnDescriptions = dto.columnDescriptions;
    if (dto.order !== undefined) data.order = dto.order;

    const item = await this.prisma.meetingAgendaItem.update({
      where: { id: itemId },
      data,
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_ITEM_UPDATED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_ITEM,
      statusCode: 200,
      resourceId: item.id,
      changes: {
        entityId: item.id,
        entityType: 'AGENDA_ITEM',
        before: existing,
        after: item,
      },
    });

    return { item };
  }

  /**
   * 软删议程项 + 同事务 cascade 软删该项下所有 task / attachment。
   */
  async deleteItem(
    sectionId: string,
    itemId: string,
    actor: { userId: string },
    req?: Request,
  ): Promise<void> {
    const existing = await this.prisma.meetingAgendaItem.findFirst({
      where: { id: itemId, sectionId, deletedAt: null },
    });
    if (!existing) throw agendaItemNotFound();

    await this.prisma.$transaction(async (tx) => {
      await tx.$executeRaw`SELECT id FROM platform_meeting_attendance.meeting_agenda_items WHERE id = ${itemId}::uuid FOR UPDATE`;

      const now = new Date();
      await tx.meetingAgendaItemUploadTask.updateMany({
        where: { agendaItemId: itemId, deletedAt: null },
        data: { deletedAt: now },
      });
      await tx.meetingAgendaItemAttachment.updateMany({
        where: { agendaItemId: itemId, deletedAt: null },
        data: { deletedAt: now },
      });
      await tx.meetingAgendaItem.update({
        where: { id: itemId },
        data: { deletedAt: now },
      });
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_ITEM_DELETED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_ITEM,
      statusCode: 204,
      resourceId: itemId,
      changes: { entityId: itemId, entityType: 'AGENDA_ITEM', before: existing },
    });
  }

  async reorderItems(
    sectionId: string,
    ids: string[],
    actor: { userId: string },
    req?: Request,
  ) {
    const section = await this.prisma.meetingAgendaSection.findFirst({
      where: { id: sectionId, deletedAt: null },
      select: { id: true },
    });
    if (!section) throw agendaSectionNotFound();

    const existing = await this.prisma.meetingAgendaItem.findMany({
      where: { sectionId, deletedAt: null },
      select: { id: true },
    });
    const existingIds = new Set(existing.map((s) => s.id));
    const idsSet = new Set(ids);
    if (existingIds.size !== idsSet.size || ![...existingIds].every((id) => idsSet.has(id))) {
      throw new MeetingAttendanceError(
        400,
        'ids must be a complete set of items under the section',
        'MEETING_ATTENDANCE_006',
      );
    }

    await this.prisma.$transaction(
      ids.map((id, idx) =>
        this.prisma.meetingAgendaItem.update({
          where: { id },
          data: { order: idx },
        }),
      ),
    );

    const items = await this.prisma.meetingAgendaItem.findMany({
      where: { sectionId, deletedAt: null },
      orderBy: { order: 'asc' },
    });

    await this.auditLogWriter.log({
      request: req,
      actor: { userId: actor.userId },
      action: MEETING_ATTENDANCE_AUDIT_ACTIONS.AGENDA_ITEM_UPDATED,
      resource: MEETING_ATTENDANCE_AUDIT_RESOURCES.AGENDA_ITEM,
      statusCode: 200,
      resourceId: sectionId,
      changes: { entityId: sectionId, entityType: 'AGENDA_ITEM_REORDER', after: { ids } },
    });

    return { sectionId, items };
  }

  // ---------- helper：从 itemId / sectionId 反查 meetingId（controller 鉴权用） ----------

  async findMeetingIdByItem(itemId: string): Promise<string | null> {
    const item = await this.prisma.meetingAgendaItem.findFirst({
      where: { id: itemId, deletedAt: null },
      include: { section: { select: { meetingId: true } } },
    });
    return item?.section?.meetingId ?? null;
  }

  async findMeetingIdBySection(sectionId: string): Promise<string | null> {
    const section = await this.prisma.meetingAgendaSection.findFirst({
      where: { id: sectionId, deletedAt: null },
      select: { meetingId: true },
    });
    return section?.meetingId ?? null;
  }
}
