import { Controller } from "@nestjs/common";
import { GrpcMethod } from "@nestjs/microservices";
import { NotificationsService } from "../notifications/notifications.service.js";
import {
  sendNotificationSchema,
  sendNotificationBatchSchema,
} from "../notifications/notifications.dto.js";
import type { Notification } from "../notifications/notifications.schema.js";

/**
 * NotificationsGrpcController —— gRPC 入口（NotificationService 9 RPC）。
 *
 * 仲裁依据：
 * - M1：gRPC 50056 启用
 * - msg.proto §NotificationService（9 RPC）
 * - proto-loader 默认 keepCase=false，字段名 camelCase
 *
 * gRPC 方法接收 proto message（camelCase 字段），调用 NotificationsService，
 * 返回 proto message（camelCase 字段，int64 时间戳为 epoch 毫秒）。
 */

// ============================================================
// 类型定义（对齐 proto msg.proto）
// ============================================================

interface GrpcNotification {
  id: string;
  userId: string;
  type: string;
  title: string;
  content: string;
  channel: string;
  isRead: boolean;
  createdAt: number;
  status: string;
  relatedEntityType: string;
  relatedEntityId: string;
  groupId: string;
  senderId: string;
  templateId: string;
  eventId: string;
  readAt: number;
  updatedAt: number;
  metadata: Record<string, string>;
}

interface GrpcBatchSendFailure {
  userId: string;
  error: string;
}

// ============================================================
// 工具函数
// ============================================================

/** proto3 默认空字符串 -> undefined（让 Zod optional 生效） */
function opt(val: unknown): string | undefined {
  if (typeof val === "string" && val.length > 0) return val;
  return undefined;
}

/** proto3 默认 0 -> undefined（让分页使用默认值） */
function optNum(val: unknown): number | undefined {
  const n = Number(val);
  return Number.isFinite(n) && n > 0 ? n : undefined;
}

/** epoch 毫秒：Date -> number，其他 -> number|0 */
function toEpoch(val: unknown): number {
  if (val instanceof Date) return val.getTime();
  const n = Number(val);
  return Number.isFinite(n) ? n : 0;
}

/**
 * 将 DB Notification 记录（camelCase + Date）或 ES 命中（snake_case）统一映射为 proto Notification。
 */
function toGrpcNotification(n: Record<string, unknown>): GrpcNotification {
  const createdAt = n.createdAt ?? n.created_at;
  const readAt = n.readAt ?? n.read_at;
  const updatedAt = n.updatedAt ?? n.updated_at;
  return {
    id: String(n.id ?? ""),
    userId: String(n.userId ?? n.user_id ?? ""),
    type: String(n.type ?? ""),
    title: String(n.title ?? ""),
    content: String(n.content ?? ""),
    channel: String(n.channel ?? ""),
    isRead: Boolean(n.isRead ?? n.is_read ?? false),
    createdAt: toEpoch(createdAt),
    status: String(n.status ?? ""),
    relatedEntityType: String(
      n.relatedEntityType ?? n.related_entity_type ?? "",
    ),
    relatedEntityId: String(n.relatedEntityId ?? n.related_entity_id ?? ""),
    groupId: String(n.groupId ?? n.group_id ?? ""),
    senderId: String(n.senderId ?? n.sender_id ?? ""),
    templateId: String(n.templateId ?? n.template_id ?? ""),
    eventId: String(n.eventId ?? n.event_id ?? ""),
    readAt: toEpoch(readAt),
    updatedAt: toEpoch(updatedAt),
    metadata: (n.metadata ?? {}) as Record<string, string>,
  };
}

// ============================================================
// Controller
// ============================================================

@Controller()
export class NotificationsGrpcController {
  constructor(private readonly service: NotificationsService) {}

  // ----------------------------------------------------------
  // SendNotification
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "SendNotification")
  async sendNotification(
    data: Record<string, unknown>,
  ): Promise<GrpcNotification> {
    const dto = sendNotificationSchema.parse({
      userId: data.userId,
      type: data.type,
      title: data.title,
      content: data.content,
      channel: opt(data.channel),
      metadata: data.metadata,
      relatedEntityType: opt(data.relatedEntityType),
      relatedEntityId: opt(data.relatedEntityId),
      groupId: opt(data.groupId),
      senderId: opt(data.senderId),
      templateId: opt(data.templateId),
      eventId: opt(data.eventId),
    });

    const result = await this.service.send(dto);
    const now = Date.now();

    return {
      id: result.id,
      userId: dto.userId,
      type: dto.type,
      title: dto.title,
      content: dto.content,
      channel: dto.channel ?? "in_app",
      isRead: false,
      createdAt: now,
      status: result.status,
      relatedEntityType: dto.relatedEntityType ?? "",
      relatedEntityId: dto.relatedEntityId ?? "",
      groupId: dto.groupId ?? "",
      senderId: dto.senderId ?? "",
      templateId: dto.templateId ?? "",
      eventId: dto.eventId ?? "",
      readAt: 0,
      updatedAt: now,
      metadata: dto.metadata ?? {},
    };
  }

  // ----------------------------------------------------------
  // BatchSendNotification
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "BatchSendNotification")
  async batchSendNotification(data: Record<string, unknown>): Promise<{
    ids: string[];
    failed: GrpcBatchSendFailure[];
  }> {
    const items = Array.isArray(data.items) ? data.items : [];
    const dto = sendNotificationBatchSchema.parse({
      items: items.map((item: Record<string, unknown>) => ({
        userId: item.userId,
        type: item.type,
        title: item.title,
        content: item.content,
        channel: opt(item.channel),
        metadata: item.metadata,
        relatedEntityType: opt(item.relatedEntityType),
        relatedEntityId: opt(item.relatedEntityId),
        groupId: opt(item.groupId),
        senderId: opt(item.senderId),
        templateId: opt(item.templateId),
        eventId: opt(item.eventId),
      })),
      groupId: opt(data.groupId),
    });

    const result = await this.service.sendBatch(dto);
    return {
      ids: result.ids,
      failed: result.failed,
    };
  }

  // ----------------------------------------------------------
  // ListNotifications
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "ListNotifications")
  async listNotifications(data: Record<string, unknown>): Promise<{
    notifications: GrpcNotification[];
    total: number;
  }> {
    const result = await this.service.listByUser(String(data.userId ?? ""), {
      onlyUnread: Boolean(data.onlyUnread),
      type: opt(data.type),
      page: optNum(data.page) ?? 1,
      pageSize: optNum(data.pageSize) ?? 20,
    });

    return {
      notifications: result.items.map((n: Notification) =>
        toGrpcNotification(n as unknown as Record<string, unknown>),
      ),
      total: result.total,
    };
  }

  // ----------------------------------------------------------
  // GetUnreadCount
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "GetUnreadCount")
  async getUnreadCount(
    data: Record<string, unknown>,
  ): Promise<{ count: number }> {
    const count = await this.service.getUnreadCount(String(data.userId ?? ""));
    return { count };
  }

  // ----------------------------------------------------------
  // MarkAsRead
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "MarkAsRead")
  async markAsRead(
    data: Record<string, unknown>,
  ): Promise<Record<string, never>> {
    await this.service.markAsRead(
      String(data.id ?? ""),
      String(data.userId ?? ""),
    );
    return {};
  }

  // ----------------------------------------------------------
  // BatchMarkAsRead
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "BatchMarkAsRead")
  async batchMarkAsRead(
    data: Record<string, unknown>,
  ): Promise<Record<string, never>> {
    const ids = Array.isArray(data.ids) ? (data.ids as string[]) : [];
    await this.service.batchMarkAsRead(ids, String(data.userId ?? ""));
    return {};
  }

  // ----------------------------------------------------------
  // MarkAllAsRead
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "MarkAllAsRead")
  async markAllAsRead(
    data: Record<string, unknown>,
  ): Promise<Record<string, never>> {
    await this.service.markAllAsRead(
      String(data.userId ?? ""),
      optNum(data.before),
    );
    return {};
  }

  // ----------------------------------------------------------
  // SearchNotifications
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "SearchNotifications")
  async searchNotifications(data: Record<string, unknown>): Promise<{
    notifications: GrpcNotification[];
    total: number;
  }> {
    const result = await this.service.search(
      String(data.userId ?? ""),
      String(data.query ?? ""),
      {
        type: opt(data.type),
        page: optNum(data.page) ?? 1,
        pageSize: optNum(data.pageSize) ?? 20,
      },
    );

    return {
      notifications: result.items.map((n) => toGrpcNotification(n)),
      total: result.total,
    };
  }

  // ----------------------------------------------------------
  // RecallNotification
  // ----------------------------------------------------------
  @GrpcMethod("NotificationService", "RecallNotification")
  async recallNotification(data: Record<string, unknown>): Promise<{
    recalledCount: number;
  }> {
    const recalledCount = await this.service.recall(String(data.groupId ?? ""));
    return { recalledCount };
  }
}
