import { onRequest } from 'firebase-functions/v2/https';
import { logger } from 'firebase-functions/v2';
import { getFirestore, Query, DocumentData } from 'firebase-admin/firestore';
import { initializeApp, getApps } from 'firebase-admin/app';

import { corsMiddlewareHandler, ALLOWED_COLLECTIONS } from '../config';
import {
  isValidBoardItem,
  encodeCursor,
  decodeCursor,
  buildQueryWithAccessControl,
  verifySessionFromRequest,
} from '../utils';

import type { BoardItem, BoardAccessMode } from '../types/boardItem';

if (!getApps().length) {
  initializeApp();
}

const db = getFirestore();

export const fetchBoards = onRequest(
  { region: 'asia-northeast3' },
  async (request, response) => {
    return corsMiddlewareHandler(request, response, async () => {
      logger.info('fetchBoards: CORS‑enabled function called');

      /* 1.Method guard ------------------------------------------------------------------ */
      if (request.method !== 'GET') {
        response.status(405).send({ message: 'Method Not Allowed' });
        return;
      }

      /* 2.Auth (optional) ---------------------------------------------------------------- */
      let userRole = 0;
      let userId: string | null = null;

      const authUser = await verifySessionFromRequest(request);
      if (authUser) {
        userId = authUser.uid;
        userRole = authUser.role;
      }

      /* 3.Read & validate query params --------------------------------------------------- */
      const {
        collection = '',
        sortOrder = 'desc',
        itemsPerPage = '10',
        access = 'public',
        pageToken,
      } = request.query as Record<string, string>;

      // ❶collection allow‑list
      if (
        !ALLOWED_COLLECTIONS.has(
          collection as unknown as typeof ALLOWED_COLLECTIONS extends Set<
            infer U
          >
            ? U
            : never,
        )
      ) {
        response.status(400).send({ message: 'Invalid collection' });
        return;
      }

      // ❷access must be one of three strings
      if (!['public', 'private', 'admin'].includes(access)) {
        response.status(400).send({ message: 'Bad access mode' });
        return;
      }

      // ❸numeric arguments
      const limit = Math.max(1, Math.min(Number(itemsPerPage) || 10, 100));
      const order = sortOrder === 'asc' ? 'asc' : 'desc';

      /* 4.Build query with access control ----------------------------------------------- */
      const accessParam = (access ?? 'public') as BoardAccessMode;
      const effectiveAccess = userId ? accessParam : 'public'; // force public if anonymous

      let queryRef: Query<DocumentData>;
      try {
        queryRef = buildQueryWithAccessControl(
          db.collection(collection),
          effectiveAccess,
          userId,
          userRole,
        );
      } catch (err: Error | unknown) {
        response
          .status(
            err instanceof Error && err.message?.includes('Unauthorized')
              ? 401
              : 403,
          )
          .send({
            message:
              err instanceof Error ? err.message : 'Access control failed',
          });
        return;
      }

      queryRef = queryRef
        .orderBy('announcement', 'desc')
        .orderBy('created', order)
        .orderBy('__name__', 'asc'); // tie‑breaker for deterministic order

      /* 5. Apply cursor if we got one ---------------------------------------- */
      if (pageToken) {
        try {
          const [ann, createdTs, docId] = decodeCursor(pageToken);
          queryRef = queryRef.startAfter(ann, createdTs, docId);
        } catch {
          response.status(400).send({ message: 'Invalid pageToken' });
          return;
        }
      } /* 6. Execute query & build response ------------------------------------ */
      try {
        const snap = await queryRef.limit(limit).get();

        const items: BoardItem[] = snap.docs
          .map((d) => {
            const data = d.data();
            if (!isValidBoardItem(data)) {
              logger.warn(`fetchBoardsCursor: skip invalid ${d.id}`);
              return null;
            }
            return { docId: d.id, ...data };
          })
          .filter(<T,>(x: T): x is NonNullable<T> => x !== null);

        // nextPageToken logic
        const lastDoc = snap.docs[snap.docs.length - 1];
        const nextPageToken =
          snap.size === limit && lastDoc
            ? encodeCursor(
                lastDoc.get('announcement'),
                lastDoc.get('created'),
                lastDoc.id,
              )
            : null;

        if (effectiveAccess === 'public')
          response.set('Cache-Control', 'public,max-age=60');

        response.status(200).send({ items, nextPageToken });
      } catch (err) {
        logger.error('fetchBoards: Firestore error', err);
        response.status(500).send({ message: 'Internal Server Error' });
      }
    });
  },
);
