import { Injectable } from "@nestjs/common";
import { randomUUID } from "node:crypto";
import { eq, and, desc, sql } from "drizzle-orm";
import { db } from "../config/database.js";
import {
  notifications,
  notificationPreferences,
} from "./notifications.schema.js";
import type {
  Notification,
  NotificationPreference,
} from "./notifications.schema.js";
import type { SendNotificationDto } from "./notifications.dto.js";
import { safeIndex, safeSearch } from "../config/elasticsearch.js";
import { env } from "../config/env.js";
import { logger } from "../shared/observability/logger.js";

export interface SendResult {
  id: string;
  skipped: boolean;
  pushed: boolean;
}

export interface PaginatedResult {
  items: Notification[];
  total: number;
  page: number;
  pageSize: number;
}

@Injectable()
export class NotificationsService {
  async send(dto: SendNotificationDto): Promise<SendResult> {
    const id = randomUUID();
    const channel = dto.channel ?? "in_app";

    const [pref] = await db
      .select()
      .from(notificationPreferences)
      .where(eq(notificationPreferences.userId, dto.userId));

    if (pref && !this.isChannelEnabled(pref, channel)) {
      logger.info(
        { userId: dto.userId, channel },
        "Notification skipped by preference",
      );
      return { id, skipped: true, pushed: false };
    }

    await db.insert(notifications).values({
      id,
      userId: dto.userId,
      type: dto.type,
      title: dto.title,
      content: dto.content,
      channel,
      metadata: dto.metadata,
    });

    await safeIndex({
      index: "notifications",
      id,
      document: {
        userId: dto.userId,
        type: dto.type,
        title: dto.title,
        content: dto.content,
        channel,
        createdAt: new Date().toISOString(),
      },
    });

    const pushed = await this.notifyPushGateway(dto, channel);

    return { id, skipped: false, pushed };
  }

  async createBatch(dtos: SendNotificationDto[]): Promise<SendResult[]> {
    const results: SendResult[] = [];
    for (const dto of dtos) {
      const result = await this.send(dto);
      results.push(result);
    }
    return results;
  }

  async listByUser(
    userId: string,
    onlyUnread: boolean = false,
  ): Promise<Notification[]> {
    const conditions = onlyUnread
      ? and(eq(notifications.userId, userId), eq(notifications.isRead, false))
      : eq(notifications.userId, userId);
    return db
      .select()
      .from(notifications)
      .where(conditions)
      .orderBy(desc(notifications.createdAt));
  }

  async listByUserWithPagination(
    userId: string,
    page: number,
    pageSize: number,
  ): Promise<PaginatedResult> {
    const offset = (page - 1) * pageSize;
    const items = await db
      .select()
      .from(notifications)
      .where(eq(notifications.userId, userId))
      .orderBy(desc(notifications.createdAt))
      .limit(pageSize)
      .offset(offset);

    const [countRow] = await db
      .select({ count: sql<number>`count(*)` })
      .from(notifications)
      .where(eq(notifications.userId, userId));

    const total = countRow ? Number(countRow.count) : 0;

    return { items, total, page, pageSize };
  }

  async markAsRead(id: string): Promise<void> {
    await db
      .update(notifications)
      .set({ isRead: true })
      .where(eq(notifications.id, id));
  }

  async search(userId: string, query: string): Promise<unknown[]> {
    const result = await safeSearch("notifications", {
      bool: {
        must: [
          { term: { userId } },
          {
            multi_match: {
              query,
              fields: ["title", "content"],
            },
          },
        ],
      },
    });
    return result.hits;
  }

  private isChannelEnabled(
    pref: NotificationPreference,
    channel: string,
  ): boolean {
    switch (channel) {
      case "email":
        return pref.emailEnabled;
      case "sms":
        return pref.smsEnabled;
      case "push":
        return pref.pushEnabled;
      case "in_app":
        return pref.inAppEnabled;
      default:
        return true;
    }
  }

  /**
   * 调用 Push Gateway 推送通知。Push Gateway 不可用时 try/catch 跳过（降级模式）。
   * 仅在 PUSH_GATEWAY_URL 配置且 channel 为 push 或 in_app 时触发。
   */
  private async notifyPushGateway(
    dto: SendNotificationDto,
    channel: string,
  ): Promise<boolean> {
    if (!env.PUSH_GATEWAY_URL) return false;
    if (channel !== "push" && channel !== "in_app") return false;

    try {
      const res = await fetch(`${env.PUSH_GATEWAY_URL}/internal/push`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          userId: dto.userId,
          type: dto.type,
          title: dto.title,
          content: dto.content,
          channel,
          metadata: dto.metadata,
        }),
      });
      if (!res.ok) {
        logger.warn(
          { status: res.status },
          "Push gateway returned non-ok status",
        );
        return false;
      }
      return true;
    } catch (err) {
      logger.warn({ err }, "Push gateway unavailable, skipping push");
      return false;
    }
  }
}
