/**
 * Notifications Resolver - myNotifications Query + markNotificationAsRead Mutation (P5 扩展).
 *
 * 仲裁依据:
 *   - student-bff.schema.graphql Query.myNotifications / myNotificationUnreadCount
 *     + Mutation.markNotificationAsRead
 *   - coord-final-decisions §2 B2 (gRPC 调用 msg)
 *   - coord-final-decisions §2 B4 (强制自我越权防御, 通知仅本人可读/操作)
 *   - coord-final-decisions §2 B6 (Redis 5-30s 短缓存)
 *   - president-final-rulings §2.3 (跨阶段扩展例外)
 */
import { z } from "zod";
import type { StudentBffContext } from "../../shared/graphql/yoga.js";
import { ok, fail } from "../../shared/action-state.js";
import { CacheTTL } from "../../shared/cache/cache.module.js";
import {
  UnauthorizedError,
  ValidationError,
} from "../../shared/errors/application-error.js";
import { assertOwnData } from "../guards/authorization.guard.js";

const MarkNotificationReadInputSchema = z.object({
  notificationId: z.string().min(1),
  studentId: z.string().min(1).optional(),
});

export const notificationsResolvers = {
  Query: {
    /**
     * myNotifications: 消息列表.
     * @permission STUDENT_NOTIFICATION_READ
     * @dataScope OWN
     */
    async myNotifications(
      _parent: unknown,
      args: { page?: number; pageSize?: number; unreadOnly?: boolean },
      ctx: StudentBffContext,
    ): Promise<unknown> {
      if (!ctx.userId) {
        throw new UnauthorizedError();
      }

      const page = args.page ?? 1;
      const pageSize = Math.min(args.pageSize ?? 20, 50);
      const cacheKey = `${ctx.userId}:${page}:${args.unreadOnly ?? false}`;

      const cached = await ctx.cache.get<unknown>("notifications", cacheKey);
      if (cached) {
        return ok(cached, {
          traceId: ctx.traceId,
          cachedAt: new Date().toISOString(),
        });
      }

      try {
        const result = await ctx.downstream.call(
          "msg",
          "ListNotifications",
          {
            userId: ctx.userId,
            page,
            pageSize,
            unreadOnly: args.unreadOnly ?? false,
          },
          {
            traceId: ctx.traceId,
            metadata: { "x-user-id": ctx.userId },
          },
        );

        const data = result as {
          notifications: unknown[];
          totalCount: number;
          unreadCount: number;
        };
        await ctx.cache.set(
          "notifications",
          data,
          CacheTTL.NOTIFICATIONS,
          cacheKey,
        );
        return ok(data, { traceId: ctx.traceId });
      } catch (err) {
        return fail(
          "BFF_STUDENT_BAD_GATEWAY",
          `Failed to fetch notifications: ${(err as Error).message}`,
          { i18nKey: "error.bffStudent.bad_gateway", traceId: ctx.traceId },
        );
      }
    },

    /**
     * myNotificationUnreadCount: 未读通知数.
     * @permission STUDENT_NOTIFICATION_READ
     */
    async myNotificationUnreadCount(
      _parent: unknown,
      _args: unknown,
      ctx: StudentBffContext,
    ): Promise<unknown> {
      if (!ctx.userId) {
        throw new UnauthorizedError();
      }

      try {
        const result = await ctx.downstream.call(
          "msg",
          "GetUnreadCount",
          {
            userId: ctx.userId,
          },
          {
            traceId: ctx.traceId,
            metadata: { "x-user-id": ctx.userId },
          },
        );

        const data = result as { unreadCount: number };
        return ok(data, { traceId: ctx.traceId });
      } catch (err) {
        return fail(
          "BFF_STUDENT_BAD_GATEWAY",
          `Failed to fetch unread count: ${(err as Error).message}`,
          { i18nKey: "error.bffStudent.bad_gateway", traceId: ctx.traceId },
        );
      }
    },
  },

  Mutation: {
    /**
     * markNotificationAsRead: 标记通知已读.
     * @permission STUDENT_NOTIFICATION_READ
     * @dataScope OWN (B4 防御 body 中 studentId)
     */
    async markNotificationAsRead(
      _parent: unknown,
      args: { input: unknown },
      ctx: StudentBffContext,
    ): Promise<unknown> {
      if (!ctx.userId) {
        throw new UnauthorizedError();
      }

      const parseResult = MarkNotificationReadInputSchema.safeParse(args.input);
      if (!parseResult.success) {
        throw new ValidationError(
          "Invalid markNotificationAsRead input",
          parseResult.error.flatten(),
        );
      }
      const input = parseResult.data;

      // B4 越权防御
      assertOwnData(ctx.userId, input.studentId);

      try {
        const result = await ctx.downstream.call(
          "msg",
          "MarkNotificationAsRead",
          {
            notificationId: input.notificationId,
            userId: ctx.userId,
          },
          {
            traceId: ctx.traceId,
            metadata: { "x-user-id": ctx.userId },
          },
        );

        // 失效通知缓存
        await ctx.cache.invalidateByPrefix(`notifications:${ctx.userId}`);

        return ok(result, { traceId: ctx.traceId });
      } catch (err) {
        return fail(
          "BFF_STUDENT_BAD_GATEWAY",
          `Failed to mark notification as read: ${(err as Error).message}`,
          { i18nKey: "error.bffStudent.bad_gateway", traceId: ctx.traceId },
        );
      }
    },
  },
};
