import { NextResponse } from 'next/server';
import { withErrorHandler } from '@server/middleware/withErrorHandler';
import { withAuth } from '@server/middleware/withAuth';
import { withValidationAuthed } from '@server/middleware/withValidation';
import { switchBranchSchema, type SwitchBranchInput } from '@server/validators/branches.validator';
import { createSession, COOKIE_NAME } from '@server/auth';
import { getBranch } from '@server/services/branches.service';
import { ForbiddenError, ValidationError } from '@server/errors';
import { db } from '@server/db/drizzle';
import { sql } from 'drizzle-orm';

export const POST = withErrorHandler(
  withAuth(
    withValidationAuthed(switchBranchSchema, async (req) => {
      const { branchId } = req.parsedBody as SwitchBranchInput;
      const { session } = req;

      if (!session.restaurantId) throw new ValidationError('No restaurant in session');

      // Re-read role + users.branch_id from DB so stale JWTs and admin
      // reassignments take effect immediately. Owners are exempt — their
      // users.branch_id is just a default landing branch.
      const { rows: userRows } = await db.execute(sql`
        SELECT role, branch_id, is_active FROM users WHERE id = ${session.userId} LIMIT 1
      `);
      const dbRow = userRows[0] as
        | { role?: string | null; branch_id?: string | null; is_active?: boolean | null }
        | undefined;

      // Fail closed: missing or deactivated user must not be able to mint a
      // fresh token at all. (withAuth typically catches this, but a user
      // could be deactivated between the auth check and this query.)
      if (!dbRow || dbRow.is_active === false) {
        throw new ForbiddenError('Account is no longer active.');
      }
      const dbRole = dbRow.role ?? null;
      const dbPinnedBranchId = dbRow.branch_id ?? null;

      if (branchId === null) {
        // "All branches" / restaurant-wide view. Strictly owner-only —
        // staff/managers are pinned to a single branch and must never be
        // able to clear that pin (defense in depth: even if the UI gate
        // is bypassed, this 403s).
        if (dbRole !== 'owner') {
          throw new ForbiddenError('You are restricted to your assigned branch.');
        }
      } else {
        // Switching to a specific branch — keep the existing pinned-branch
        // guard for non-owners, then verify the branch belongs to the
        // restaurant and is active.
        if (dbRole !== 'owner' && dbPinnedBranchId && branchId !== dbPinnedBranchId) {
          throw new ForbiddenError('You are restricted to your assigned branch.');
        }
        const branch = await getBranch(branchId, session.restaurantId);
        if (!branch.is_active) throw new ValidationError('Cannot switch to an inactive branch');
      }

      // Reissue the token using DB-authoritative claims rather than spreading
      // potentially stale JWT claims. This way a user demoted from owner to
      // staff (or vice versa) between sessions doesn't carry forward an
      // elevated role/pinnedBranchId from the previous JWT — every
      // switch-branch reconciles role + pin from the database. The role
      // gates above already 403 the dangerous null-branch case for
      // non-owners, so by the time we reach here the requested branchId is
      // safe for this DB-confirmed role.
      const newToken = await createSession({
        userId: session.userId,
        email: session.email,
        role: dbRole ?? session.role,
        restaurantId: session.restaurantId,
        branchId,
        pinnedBranchId: dbRole === 'owner' ? null : dbPinnedBranchId,
      });

      const res = NextResponse.json({ ok: true });
      res.cookies.set(COOKIE_NAME, newToken, {
        httpOnly: true,
        sameSite: 'lax',
        secure: process.env.NODE_ENV === 'production',
        path: '/',
        maxAge: 60 * 60 * 24 * 7,
      });
      return res;
    })
  )
);
