import { ConvexError, v } from 'convex/values';
import { getAuthUserId } from '@convex-dev/auth/server';
import {
  type MutationCtx,
  type QueryCtx,
  action,
  mutation,
  query,
} from './_generated/server';
import { api } from './_generated/api';
import type { Doc, Id } from './_generated/dataModel';
import { paginationOptsValidator } from 'convex/server';

type RWCtx = MutationCtx | QueryCtx;

type StatusRow = {
  user: {
    id: Id<'users'>;
    email: string | null;
    name: string | null;
    imageUrl: string | null;
  };
  status: {
    id: Id<'statuses'>;
    message: string;
    updatedAt: number;
    updatedBy: StatusRow['user'] | null;
  } | null;
};

type Paginated<T> = {
  page: T[];
  isDone: boolean;
  continueCursor: string | null;
};

// CHANGED: typed helpers
const ensureUser = async (ctx: RWCtx, userId: Id<'users'>) => {
  const user = await ctx.db.get(userId);
  if (!user) throw new ConvexError('User not found.');
  return user;
};

const latestStatusForOwner = async (ctx: RWCtx, ownerId: Id<'users'>) => {
  const [latest] = await ctx.db
    .query('statuses')
    .withIndex('by_user_updatedAt', (q) => q.eq('userId', ownerId))
    .order('desc')
    .take(1);
  return latest as Doc<'statuses'> | null;
};

/**
 * Create a new status for a single user.
 * - Defaults userId to the caller.
 * - updatedBy defaults to the caller.
 * - Updates the user's currentStatusId pointer.
 */
export const create = mutation({
  args: {
    message: v.string(),
    userId: v.optional(v.id('users')),
    updatedBy: v.optional(v.id('users')),
  },
  handler: async (ctx, args) => {
    const authUserId = await getAuthUserId(ctx);
    if (!authUserId) throw new ConvexError('Not authenticated.');

    const userId = args.userId ?? authUserId;
    await ensureUser(ctx, userId);

    const updatedBy = args.updatedBy ?? authUserId;
    await ensureUser(ctx, updatedBy);

    const message = args.message.trim();
    if (message.length === 0) {
      throw new ConvexError('Message cannot be empty.');
    }

    const statusId = await ctx.db.insert('statuses', {
      message,
      userId,
      updatedBy,
      updatedAt: Date.now(),
    });

    await ctx.db.patch(userId, { currentStatusId: statusId });

    return { statusId };
  },
});

/**
 * Bulk create the same status for many users.
 * - updatedBy defaults to the caller.
 * - Updates each user's currentStatusId pointer.
 */
export const bulkCreate = mutation({
  args: {
    message: v.string(),
    userIds: v.array(v.id('users')),
    updatedBy: v.optional(v.id('users')),
  },
  handler: async (ctx, args) => {
    const authUserId = await getAuthUserId(ctx);
    if (!authUserId) throw new ConvexError('Not authenticated.');

    if (args.userIds.length === 0) return { statusIds: [] };

    const updatedBy = args.updatedBy ?? authUserId;
    await ensureUser(ctx, updatedBy);

    const message = args.message.trim();
    if (message.length === 0) {
      throw new ConvexError('Message cannot be empty.');
    }

    const statusIds: Id<'statuses'>[] = [];
    const now = Date.now();

    // Sequential to keep load predictable; switch to Promise.all
    // if your ownerIds lists are small and bounded.
    for (const userId of args.userIds) {
      await ensureUser(ctx, userId);

      const statusId = await ctx.db.insert('statuses', {
        message,
        userId,
        updatedBy,
        updatedAt: now,
      });

      await ctx.db.patch(userId, { currentStatusId: statusId });
      statusIds.push(statusId);
    }
    return { statusIds };
  },
});

/**
 * Update all statuses for all users.
 */
export const updateAllStatuses = mutation({
  args: { message: v.string() },
  handler: async (ctx, args) => {
    const userIds = await ctx.runQuery(api.auth.getAllUserIds);
    const updatedAt = Date.now();
    const statusIds: Id<'statuses'>[] = [];
    for (const userId of userIds) {
      await ensureUser(ctx, userId);
      const statusId = await ctx.db.insert('statuses', {
        message: args.message,
        userId,
        updatedAt,
      });
      await ctx.db.patch(userId, { currentStatusId: statusId });
      statusIds.push(statusId);
    }
    return { statusIds };
  },
});

/**
 * Current status for a specific user.
 * - Uses users.currentStatusId if present,
 *   otherwise falls back to latest by index.
 */
export const getCurrentForUser = query({
  args: { userId: v.id('users') },
  handler: async (ctx, { userId }) => {
    const user = await ensureUser(ctx, userId);

    if (user.currentStatusId) {
      const status = await ctx.db.get(user.currentStatusId);
      if (status) return status;
    }

    return await latestStatusForOwner(ctx, userId);
  },
});

const getName = (u: Doc<'users'>): string | null =>
  'name' in u && typeof u.name === 'string' ? u.name : null;
const getEmail = (u: Doc<'users'>): string | null =>
  'email' in u && typeof u.email === 'string' ? u.email : null;

const getImageId = (u: Doc<'users'>): Id<'_storage'> | null => {
  if (!('image' in u)) return null;
  const img = (u as { image?: unknown }).image as string | undefined;
  return img && img.length > 0 ? (img as Id<'_storage'>) : null;
};

/**
 * Current statuses for all users.
 * - Reads each user's currentStatusId pointer.
 * - Falls back to latest-by-index if pointer is missing.
 */
export const getCurrentForAll = query({
  args: {},
  handler: async (ctx): Promise<StatusRow[]> => {
    const users = await ctx.db.query('users').collect();

    return await Promise.all(
      users.map(async (u) => {
        // Resolve user's current or latest status
        let curStatus: Doc<'statuses'> | null = null;
        if ('currentStatusId' in u && u.currentStatusId) {
          curStatus = await ctx.db.get(u.currentStatusId);
        }
        if (!curStatus) {
          const [latest] = await ctx.db
            .query('statuses')
            .withIndex('by_user_updatedAt', (q) => q.eq('userId', u._id))
            .order('desc')
            .take(1);
          curStatus = latest ?? null;
        }

        // User display + URL
        const userImageId = getImageId(u);
        const userImageUrl = userImageId
          ? await ctx.storage.getUrl(userImageId)
          : null;

        // Updated by (if different) + URL
        let updatedByUser: StatusRow['user'] | null = null;
        if (curStatus && curStatus.updatedBy && curStatus.updatedBy !== u._id) {
          const updater = await ctx.db.get(curStatus.updatedBy);
          if (!updater) throw new ConvexError('Updater not found.');
          const updaterImageId = getImageId(updater);
          const updaterImageUrl = updaterImageId
            ? await ctx.storage.getUrl(updaterImageId)
            : null;
          updatedByUser = {
            id: updater._id,
            email: getEmail(updater),
            name: getName(updater),
            imageUrl: updaterImageUrl,
          };
        }

        const status: StatusRow['status'] = curStatus
          ? {
              id: curStatus._id,
              message: curStatus.message,
              updatedAt: curStatus.updatedAt,
              updatedBy: updatedByUser,
            }
          : null;

        return {
          user: {
            id: u._id,
            email: getEmail(u),
            name: getName(u),
            imageUrl: userImageUrl,
          },
          status,
        };
      }),
    );
  },
});

/**
 * Paginated history for all users or for a specific user.
 */
export const listHistory = query({
  args: {
    userId: v.optional(v.id('users')),
    paginationOpts: paginationOptsValidator,
  },
  handler: async (
    ctx,
    { userId, paginationOpts },
  ): Promise<Paginated<StatusRow>> => {
    // Query statuses newest-first, optionally filtered by user
    const result = userId
      ? await ctx.db
          .query('statuses')
          .withIndex('by_user_updatedAt', (q) => q.eq('userId', userId))
          .order('desc')
          .paginate(paginationOpts)
      : await ctx.db.query('statuses').order('desc').paginate(paginationOpts);

    // Cache user display objects to avoid refetching repeatedly
    const displayCache = new Map<string, StatusRow['user']>();

    const getDisplay = async (uid: Id<'users'>): Promise<StatusRow['user']> => {
      const key = uid as unknown as string;
      const cached = displayCache.get(key);
      if (cached) return cached;

      const user = await ctx.db.get(uid);
      if (!user) throw new ConvexError('User not found.');

      const imgId = getImageId(user);
      const imgUrl = imgId ? await ctx.storage.getUrl(imgId) : null;

      const display: StatusRow['user'] = {
        id: user._id,
        email: getEmail(user),
        name: getName(user),
        imageUrl: imgUrl,
      };
      displayCache.set(key, display);
      return display;
    };

    const statuses: StatusRow[] = [];
    for (const s of result.page) {
      const owner = await getDisplay(s.userId);
      const updatedBy =
        s.updatedBy && s.updatedBy !== s.userId
          ? await getDisplay(s.updatedBy)
          : null;

      statuses.push({
        user: owner,
        status: {
          id: s._id,
          message: s.message,
          updatedAt: s.updatedAt,
          updatedBy,
        },
      });
    }
    const page = statuses.sort(
      (a, b) => (b.status?.updatedAt ?? 0) - (a.status?.updatedAt ?? 0),
    );

    return {
      page,
      isDone: result.isDone,
      continueCursor: result.continueCursor,
    };
  },
});

export const endOfShiftUpdate = action({
  handler: async (ctx) => {
    const now = new Date(
      new Date().toLocaleString('en-US', {
        timeZone: 'America/Chicago',
      }),
    );
    const day = now.getDay(),
      hour = now.getHours(),
      minute = now.getMinutes();
    if (day == 0 || day === 6) return;
    const message = day === 5 ? 'Enjoying the weekend' : 'End of shift';
    if (hour === 17) {
      await ctx.runMutation(api.statuses.updateAllStatuses, { message });
    } else if (hour === 16) {
      const ms = ((60 - minute) % 60) * 60 * 1000;
      await ctx.scheduler.runAfter(ms, api.statuses.endOfShiftUpdate);
    } else return;
  },
});
