import { HttpError, type RequestContext } from '../../core/http.js';
import { intParam, readJsonBody, stringParam, tenantIdFrom, userIdFrom } from '../../core/request.js';
import { query } from '../../core/db.js';
import { normalizeNotificationStatus, USER_NOTIFICATION_STATUSES } from '../notifications/service.js';

const NOTIFICATION_TYPES = [
  'feedback_status_updated',
  'feedback_reward_granted',
  'badge_granted',
  'point_exchange_completed',
  'point_exchange_pending_fulfillment',
];

function nullableString(value: unknown) {
  return typeof value === 'string' && value.trim() ? value.trim() : null;
}

function notificationIdsValue(value: unknown) {
  if (!Array.isArray(value)) throw new HttpError(400, 'notificationIds is required', 'NOTIFICATION_IDS_REQUIRED');
  const ids = value.map(item => {
    const id = nullableString(item);
    if (!id || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
      throw new HttpError(400, 'notificationIds must contain UUID values', 'INVALID_UUID');
    }
    return id;
  });
  if (ids.length === 0) throw new HttpError(400, 'notificationIds cannot be empty', 'NOTIFICATION_IDS_REQUIRED');
  if (ids.length > 100) throw new HttpError(400, 'notificationIds cannot exceed 100 items', 'NOTIFICATION_IDS_TOO_MANY');
  return Array.from(new Set(ids));
}

function notificationTypeParam(ctx: RequestContext) {
  const notificationType = stringParam(ctx, 'notificationType') || stringParam(ctx, 'type');
  if (!notificationType) return '';
  if (!NOTIFICATION_TYPES.includes(notificationType)) {
    throw new HttpError(400, `Invalid notificationType: ${notificationType}`, 'INVALID_NOTIFICATION_TYPE');
  }
  return notificationType;
}

export async function profileNotificationsRoute(ctx: RequestContext) {
  const tenantId = await tenantIdFrom(ctx);
  const userId = await userIdFrom(ctx);
  const limit = intParam(ctx, 'limit', 50, 200);
  const status = stringParam(ctx, 'status');
  const notificationType = notificationTypeParam(ctx);
  const params: unknown[] = [tenantId, userId];
  const filters = ['tenant_id = $1', 'user_id = $2'];

  if (status) {
    normalizeNotificationStatus(status);
    params.push(status);
    filters.push(`status = $${params.length}`);
  }
  if (notificationType) {
    params.push(notificationType);
    filters.push(`notification_type = $${params.length}`);
  }
  params.push(limit);

  const items = await query(
    `
    select id, notification_type as "notificationType", status, severity,
           title, message, action_label as "actionLabel",
           action_path as "actionPath", source_type as "sourceType",
           source_id as "sourceId", metadata, read_at as "readAt",
           created_at as "createdAt", updated_at as "updatedAt"
    from public.user_notifications
    where ${filters.join(' and ')}
    order by created_at desc
    limit $${params.length}
    `,
    params,
  );

  const summaryRows = await query<{ status: string; count: number }>(
    `
    select status, count(*)::int as count
    from public.user_notifications
    where tenant_id = $1 and user_id = $2
    group by status
    `,
    [tenantId, userId],
  );
  const summary = Object.fromEntries(USER_NOTIFICATION_STATUSES.map(item => [item, 0]));
  for (const row of summaryRows) summary[row.status] = Number(row.count || 0);

  return { items, summary };
}

export async function updateProfileNotificationStatusRoute(ctx: RequestContext) {
  const tenantId = await tenantIdFrom(ctx);
  const body = await readJsonBody(ctx);
  const userId = await userIdFrom(ctx, body);
  const notificationIds = notificationIdsValue(body.notificationIds);
  const status = normalizeNotificationStatus(body.status, 'read');
  if (status === 'unread') {
    throw new HttpError(400, 'Use read, dismissed or archived for notification status updates', 'INVALID_NOTIFICATION_STATUS');
  }

  const result = await query<{ id: string; notificationType: string; status: string; readAt: string | null; updatedAt: string }>(
    `
    update public.user_notifications
    set status = $4,
        read_at = case
          when $4 = 'read' then coalesce(read_at, now())
          else read_at
        end,
        updated_at = now()
    where tenant_id = $1
      and user_id = $2
      and id = any($3::uuid[])
    returning id, notification_type as "notificationType", status, read_at as "readAt", updated_at as "updatedAt"
    `,
    [tenantId, userId, notificationIds, status],
  );

  if (result.length !== notificationIds.length) {
    throw new HttpError(404, 'Some notifications were not found for this user', 'NOTIFICATION_NOT_FOUND');
  }

  return {
    item: {
      updatedCount: result.length,
      status,
      notificationIds: result.map(item => item.id),
    },
  };
}
