import { and, eq } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
import { announcements } from "@/db/schema";
import {
  canDeleteContent,
  canEditContent,
  canTransitionContent,
  getNextContentStatus,
} from "@/features/content-approval/lib/workflow";
import {
  AUDIT_ACTION,
  AUDIT_MODULE,
  getRequestMetadata,
  logAuditEvent,
  toAuditValue,
} from "@/features/audit-logs/server/audit-service";
import {
  getAnnouncementByIdForAccess,
  getAnnouncementReviewRecipientIds,
  getOrganizationAnnouncementRecipientIds,
  serializeAnnouncementInternal,
  serializeAnnouncementForAccess,
} from "@/features/announcements/server/announcement-data";
import {
  announcementDraftActionSchema,
  announcementDraftSchema,
} from "@/features/announcements/schemas/announcement";
import {
  createAnnouncementNotification,
  createContentWorkflowNotification,
} from "@/features/notifications/server/notification-service";
import { AuthError, requireOrganizationAccess } from "@/lib/auth/session";
import {
  ANNOUNCEMENT_MAX_FILE_SIZE,
  deleteStoredAnnouncementAttachment,
  saveAnnouncementAttachment,
} from "@/lib/announcement-storage";
import { getDatabaseErrorMessage } from "@/lib/db-errors";
import { db } from "@/lib/db";

const SUPPORTED_ATTACHMENT_TYPES = new Set([
  "application/pdf",
  "image/jpeg",
  "image/png",
  "application/msword",
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "application/vnd.ms-excel",
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
]);

function normalizeDateValue(value: FormDataEntryValue | null) {
  if (typeof value !== "string") return "";
  return value.trim();
}

function parseBooleanValue(value: FormDataEntryValue | null) {
  return value === "true";
}

function validateAttachment(file: File | null) {
  if (!file || file.size === 0) return null;

  if (!SUPPORTED_ATTACHMENT_TYPES.has(file.type)) {
    throw new Error(
      "รองรับไฟล์แนบ PDF, JPG, JPEG, PNG, DOC, DOCX, XLS และ XLSX เท่านั้น",
    );
  }

  if (file.size > ANNOUNCEMENT_MAX_FILE_SIZE) {
    throw new Error("ไฟล์แนบต้องมีขนาดไม่เกิน 10MB");
  }

  return file;
}

function parseId(value: string) {
  const id = Number(value);
  if (Number.isNaN(id)) {
    throw new Error("รหัสประกาศไม่ถูกต้อง");
  }
  return id;
}

function buildWorkflowMessage(comment: string | null, fallback: string) {
  return comment ? `${fallback} หมายเหตุ: ${comment}` : fallback;
}

async function parseAnnouncementFormData(request: NextRequest) {
  const formData = await request.formData();
  const rawValues = {
    title: String(formData.get("title") ?? ""),
    content: String(formData.get("content") ?? ""),
    startDate: normalizeDateValue(formData.get("startDate")),
    endDate: normalizeDateValue(formData.get("endDate")) || undefined,
    removeAttachment: parseBooleanValue(formData.get("removeAttachment")),
  };

  const parsedValues = announcementDraftSchema.safeParse(rawValues);
  if (!parsedValues.success) {
    throw new Error(
      parsedValues.error.issues[0]?.message ?? "ข้อมูลประกาศไม่ถูกต้อง",
    );
  }

  const parsedAction = announcementDraftActionSchema.safeParse(
    String(formData.get("action") ?? "draft"),
  );
  if (!parsedAction.success) {
    throw new Error("คำสั่งการบันทึกประกาศไม่ถูกต้อง");
  }

  return {
    action: parsedAction.data,
    values: parsedValues.data,
    attachment: validateAttachment(formData.get("attachment") as File | null),
  };
}

export async function GET(
  _request: NextRequest,
  context: { params: Promise<{ id: string }> },
) {
  try {
    const { organization, session } = await requireOrganizationAccess();
    const { id: rawId } = await context.params;
    const id = parseId(rawId);

    const row = await getAnnouncementByIdForAccess({
      id,
      organizationId: organization.id,
      permissions: session.user.effectivePermissions,
    });

    if (!row) {
      return NextResponse.json({ message: "ไม่พบประกาศ" }, { status: 404 });
    }

    return NextResponse.json({
      success: true,
      time: new Date().toISOString(),
      message: "โหลดรายละเอียดประกาศเรียบร้อยแล้ว",
      announcement: serializeAnnouncementForAccess({
        row,
        permissions: session.user.effectivePermissions,
        currentUserId: session.user.id,
      }),
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return NextResponse.json(
        { message: error.message },
        { status: error.status },
      );
    }

    if (error instanceof Error) {
      return NextResponse.json({ message: error.message }, { status: 400 });
    }

    return NextResponse.json(
      { message: "ไม่สามารถโหลดรายละเอียดประกาศได้" },
      { status: 500 },
    );
  }
}

export async function PATCH(
  request: NextRequest,
  context: { params: Promise<{ id: string }> },
) {
  try {
    const { organization, session } = await requireOrganizationAccess();
    const requestMetadata = getRequestMetadata(request);
    const { id: rawId } = await context.params;
    const id = parseId(rawId);

    const [existing] = await db
      .select()
      .from(announcements)
      .where(
        and(
          eq(announcements.id, id),
          eq(announcements.organizationId, organization.id),
        ),
      )
      .limit(1);

    if (!existing) {
      return NextResponse.json({ message: "ไม่พบประกาศ" }, { status: 404 });
    }

    const permissions = session.user.effectivePermissions;
    const isOwner = existing.createdBy === session.user.id;
    const previousSerialized = serializeAnnouncementInternal({
      ...existing,
      createdByName: null,
      updatedByName: null,
    });

    const contentType = request.headers.get("content-type") ?? "";
    let nextStatus = existing.status;
    let reviewComment = existing.reviewComment;
    let multipartAction: "draft" | "submit" | null = null;
    let successMessage = "อัปเดตประกาศเรียบร้อยแล้ว";

    if (contentType.includes("multipart/form-data")) {
      if (
        !canEditContent({
          module: "announcement",
          permissions,
          status: existing.status,
          isOwner,
        })
      ) {
        return NextResponse.json(
          { message: "ไม่มีสิทธิ์แก้ไขประกาศในสถานะปัจจุบัน" },
          { status: 403 },
        );
      }

      const { action, values, attachment } =
        await parseAnnouncementFormData(request);
      multipartAction = action;

      const canSubmitAgain = canTransitionContent({
        module: "announcement",
        action: "submit",
        currentStatus: existing.status,
        permissions,
        isOwner,
      });

      if (action === "submit") {
        if (!canSubmitAgain) {
          return NextResponse.json(
            { message: "ไม่มีสิทธิ์ส่งประกาศเพื่อตรวจสอบ" },
            { status: 403 },
          );
        }

        nextStatus = getNextContentStatus("submit");
        successMessage = "ส่งประกาศเพื่อตรวจสอบเรียบร้อยแล้ว";
      } else {
        nextStatus = existing.status;
        successMessage = canSubmitAgain
          ? "บันทึกร่างประกาศเรียบร้อยแล้ว"
          : "บันทึกการแก้ไขประกาศเรียบร้อยแล้ว";
      }

      await db
        .update(announcements)
        .set({
          title: values.title.trim(),
          content: values.content.trim(),
          startDate: new Date(values.startDate),
          endDate: values.endDate?.trim() ? new Date(values.endDate) : null,
          status: nextStatus,
          submittedAt: action === "submit" ? new Date() : existing.submittedAt,
          submittedBy:
            action === "submit" ? session.user.id : existing.submittedBy,
          updatedBy: session.user.id,
          updatedAt: new Date(),
          ...(values.removeAttachment && !attachment
            ? {
                attachmentFileName: null,
                attachmentFileType: null,
                attachmentFileSize: null,
                attachmentStorageKey: null,
                attachmentFileUrl: null,
              }
            : {}),
        })
        .where(
          and(
            eq(announcements.id, id),
            eq(announcements.organizationId, organization.id),
          ),
        );

      if (
        values.removeAttachment &&
        !attachment &&
        existing.attachmentStorageKey
      ) {
        await deleteStoredAnnouncementAttachment(existing.attachmentStorageKey);
      }

      if (attachment) {
        const storedAttachment = await saveAnnouncementAttachment({
          file: attachment,
          organizationId: organization.id,
          announcementId: id,
        });

        await db
          .update(announcements)
          .set({
            attachmentFileName: storedAttachment.fileName,
            attachmentFileType: storedAttachment.fileType,
            attachmentFileSize: storedAttachment.fileSize,
            attachmentStorageKey: storedAttachment.storageKey,
            attachmentFileUrl: storedAttachment.fileUrl,
            updatedBy: session.user.id,
            updatedAt: new Date(),
          })
          .where(
            and(
              eq(announcements.id, id),
              eq(announcements.organizationId, organization.id),
            ),
          );

        if (existing.attachmentStorageKey) {
          await deleteStoredAnnouncementAttachment(
            existing.attachmentStorageKey,
          );
        }
      }
    } else {
      const body = (await request.json()) as {
        action?:
          | "submit"
          | "approve"
          | "return"
          | "reject"
          | "publish"
          | "archive";
        status?:
          | "draft"
          | "pending_review"
          | "approved"
          | "published"
          | "returned"
          | "rejected"
          | "archived";
        comment?: string;
      };

      const normalizedAction =
        body.action ??
        (body.status === "pending_review"
          ? "submit"
          : body.status === "published"
            ? "publish"
            : body.status === "archived"
              ? "archive"
              : undefined);

      if (normalizedAction === undefined) {
        return NextResponse.json(
          { message: "ไม่มีข้อมูลสำหรับอัปเดต" },
          { status: 400 },
        );
      }

      if (
        normalizedAction &&
        !canTransitionContent({
          module: "announcement",
          action: normalizedAction,
          currentStatus: existing.status,
          permissions,
          isOwner,
        })
      ) {
        return NextResponse.json(
          { message: "ไม่สามารถเปลี่ยนสถานะประกาศได้" },
          { status: 403 },
        );
      }

      nextStatus = normalizedAction
        ? getNextContentStatus(normalizedAction)
        : existing.status;
      reviewComment = body.comment?.trim() || existing.reviewComment;
      successMessage =
        normalizedAction === "publish"
          ? "เผยแพร่ประกาศเรียบร้อยแล้ว"
          : normalizedAction === "archive"
            ? "ยกเลิกเผยแพร่ออกจากหน้าบ้านเรียบร้อยแล้ว"
            : normalizedAction === "approve"
              ? "อนุมัติประกาศเรียบร้อยแล้ว"
              : normalizedAction === "return"
                ? "ส่งประกาศกลับเพื่อแก้ไขเรียบร้อยแล้ว"
                : normalizedAction === "reject"
                  ? "ปฏิเสธประกาศเรียบร้อยแล้ว"
                  : "อัปเดตประกาศเรียบร้อยแล้ว";

      await db
        .update(announcements)
        .set({
          status: nextStatus,
          isPin: normalizedAction === "archive" ? false : existing.isPin,
          submittedAt:
            normalizedAction === "submit" ? new Date() : existing.submittedAt,
          submittedBy:
            normalizedAction === "submit"
              ? session.user.id
              : existing.submittedBy,
          reviewedAt:
            normalizedAction === "approve" ||
            normalizedAction === "return" ||
            normalizedAction === "reject"
              ? new Date()
              : existing.reviewedAt,
          reviewedBy:
            normalizedAction === "approve" ||
            normalizedAction === "return" ||
            normalizedAction === "reject"
              ? session.user.id
              : existing.reviewedBy,
          approvedAt:
            normalizedAction === "approve" ? new Date() : existing.approvedAt,
          approvedBy:
            normalizedAction === "approve"
              ? session.user.id
              : existing.approvedBy,
          publishedAt:
            normalizedAction === "publish"
              ? (existing.publishedAt ?? new Date())
              : existing.publishedAt,
          publishedBy:
            normalizedAction === "publish"
              ? session.user.id
              : existing.publishedBy,
          reviewComment,
          updatedBy: session.user.id,
          updatedAt: new Date(),
        })
        .where(
          and(
            eq(announcements.id, id),
            eq(announcements.organizationId, organization.id),
          ),
        );
    }

    const detailRow = await getAnnouncementByIdForAccess({
      id,
      organizationId: organization.id,
      permissions: session.user.effectivePermissions,
    });

    if (!detailRow) {
      throw new Error("ไม่พบข้อมูลประกาศหลังอัปเดต");
    }

    const serializedAnnouncement = serializeAnnouncementInternal(detailRow);

    await logAuditEvent({
      organizationId: organization.id,
      userId: session.user.id,
      module: AUDIT_MODULE.ANNOUNCEMENTS,
      action: AUDIT_ACTION.ANNOUNCEMENT_UPDATE,
      entityName: detailRow.title,
      entityId: detailRow.id,
      oldValue: toAuditValue(previousSerialized),
      newValue: toAuditValue(serializedAnnouncement),
      ...requestMetadata,
    });

    if (multipartAction === "draft") {
      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_DRAFT_SAVED,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });
    }

    if (
      existing.status !== "pending_review" &&
      nextStatus === "pending_review"
    ) {
      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_SUBMIT,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });

      const reviewerIds = await getAnnouncementReviewRecipientIds({
        organizationId: organization.id,
        excludeUserIds: [session.user.id],
      });

      await Promise.all(
        reviewerIds.map((userId) =>
          createContentWorkflowNotification({
            organizationId: organization.id,
            userId,
            title: `มีประกาศใหม่รอตรวจสอบ: ${detailRow.title}`,
            message:
              "มีการส่งประกาศเข้าสู่คิวตรวจสอบ กรุณาเปิดเพื่อตรวจสอบและอนุมัติ",
            type: "CONTENT_SUBMITTED",
            referenceId: detailRow.id,
            referenceType: "content_submission",
          }),
        ),
      );
    }

    if (existing.status !== "approved" && nextStatus === "approved") {
      await createContentWorkflowNotification({
        organizationId: organization.id,
        userId: detailRow.createdBy,
        title: `ประกาศได้รับการอนุมัติ: ${detailRow.title}`,
        message: buildWorkflowMessage(
          reviewComment,
          "ประกาศของคุณผ่านการอนุมัติแล้ว",
        ),
        type: "CONTENT_APPROVED",
        referenceId: detailRow.id,
        referenceType: "content_approval",
      });

      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_APPROVE,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });
    }

    if (existing.status !== "returned" && nextStatus === "returned") {
      await createContentWorkflowNotification({
        organizationId: organization.id,
        userId: detailRow.createdBy,
        title: `ประกาศถูกส่งกลับแก้ไข: ${detailRow.title}`,
        message: buildWorkflowMessage(
          reviewComment,
          "กรุณาปรับแก้และส่งตรวจสอบอีกครั้ง",
        ),
        type: "CONTENT_RETURNED",
        referenceId: detailRow.id,
        referenceType: "content_approval",
      });

      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_RETURN,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });
    }

    if (existing.status !== "rejected" && nextStatus === "rejected") {
      await createContentWorkflowNotification({
        organizationId: organization.id,
        userId: detailRow.createdBy,
        title: `ประกาศไม่ผ่านการอนุมัติ: ${detailRow.title}`,
        message: buildWorkflowMessage(reviewComment, "กรุณาปรับแก้ก่อนส่งใหม่"),
        type: "CONTENT_REJECTED",
        referenceId: detailRow.id,
        referenceType: "content_approval",
      });

      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_REJECT,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });
    }

    if (existing.status !== "published" && nextStatus === "published") {
      const userIds = await getOrganizationAnnouncementRecipientIds(
        organization.id,
      );

      await createAnnouncementNotification({
        organizationId: organization.id,
        userIds,
        title: `ประกาศใหม่: ${detailRow.title}`,
        message: "มีประกาศใหม่เผยแพร่แล้ว กรุณาเปิดอ่านรายละเอียด",
        referenceId: detailRow.id,
      });

      await createContentWorkflowNotification({
        organizationId: organization.id,
        userId: detailRow.createdBy,
        title: `ประกาศถูกเผยแพร่แล้ว: ${detailRow.title}`,
        message: "ประกาศของคุณถูกเผยแพร่เรียบร้อยแล้ว",
        type: "CONTENT_PUBLISHED",
        referenceId: detailRow.id,
        referenceType: "content_published",
      });

      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_PUBLISH,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });
    }

    if (existing.status !== "archived" && nextStatus === "archived") {
      await logAuditEvent({
        organizationId: organization.id,
        userId: session.user.id,
        module: AUDIT_MODULE.ANNOUNCEMENTS,
        action: AUDIT_ACTION.ANNOUNCEMENT_ARCHIVE,
        entityName: detailRow.title,
        entityId: detailRow.id,
        oldValue: toAuditValue(previousSerialized),
        newValue: toAuditValue(serializedAnnouncement),
        ...requestMetadata,
      });
    }

    const responseAnnouncement = serializeAnnouncementForAccess({
      row: detailRow,
      permissions: session.user.effectivePermissions,
      currentUserId: session.user.id,
    });

    return NextResponse.json({
      success: true,
      time: new Date().toISOString(),
      message: successMessage,
      announcement: responseAnnouncement,
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return NextResponse.json(
        { message: error.message },
        { status: error.status },
      );
    }

    if (error instanceof Error && !error.message.includes("Database")) {
      return NextResponse.json({ message: error.message }, { status: 400 });
    }

    console.error("Failed to update announcement", error);
    return NextResponse.json(
      { message: getDatabaseErrorMessage(error) },
      { status: 500 },
    );
  }
}

export async function DELETE(
  request: NextRequest,
  context: { params: Promise<{ id: string }> },
) {
  try {
    const { organization, session } = await requireOrganizationAccess();
    const requestMetadata = getRequestMetadata(request);
    const { id: rawId } = await context.params;
    const id = parseId(rawId);

    const [existing] = await db
      .select()
      .from(announcements)
      .where(
        and(
          eq(announcements.id, id),
          eq(announcements.organizationId, organization.id),
        ),
      )
      .limit(1);

    if (!existing) {
      return NextResponse.json({ message: "ไม่พบประกาศ" }, { status: 404 });
    }

    if (
      !canDeleteContent({
        module: "announcement",
        permissions: session.user.effectivePermissions,
        status: existing.status,
        isOwner: existing.createdBy === session.user.id,
      })
    ) {
      return NextResponse.json(
        { message: "ไม่มีสิทธิ์ยกเลิกเผยแพร่นี้" },
        { status: 403 },
      );
    }

    await db
      .delete(announcements)
      .where(
        and(
          eq(announcements.id, id),
          eq(announcements.organizationId, organization.id),
        ),
      );

    if (existing.attachmentStorageKey) {
      await deleteStoredAnnouncementAttachment(existing.attachmentStorageKey);
    }

    await logAuditEvent({
      organizationId: organization.id,
      userId: session.user.id,
      module: AUDIT_MODULE.ANNOUNCEMENTS,
      action: AUDIT_ACTION.ANNOUNCEMENT_DELETE,
      entityName: existing.title,
      entityId: existing.id,
      oldValue: toAuditValue(
        serializeAnnouncementInternal({
          ...existing,
          createdByName: null,
          updatedByName: null,
        }),
      ),
      newValue: null,
      ...requestMetadata,
    });

    return NextResponse.json({
      success: true,
      message: "ยกเลิกเผยแพร่เรียบร้อยแล้ว",
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return NextResponse.json(
        { message: error.message },
        { status: error.status },
      );
    }

    if (error instanceof Error && !error.message.includes("Database")) {
      return NextResponse.json({ message: error.message }, { status: 400 });
    }

    console.error("Failed to delete announcement", error);
    return NextResponse.json(
      { message: getDatabaseErrorMessage(error) },
      { status: 500 },
    );
  }
}
