import type { Request, Response, NextFunction } from 'express';
import { ORG_ADMIN_ROLE } from '../config/access.js';
import prisma from '../config/db.js';
import { firebaseAuth } from '../config/firebase.js';
import logger from '../config/logger.js';
import * as inviteModel from '../models/invite.model.js';
import * as organizationInviteModel from '../models/organizationInvite.model.js';
import { buildSession, findSessionUser, roleLabel } from '../models/session.model.js';
import { signInWithPassword, FirebaseRestError } from '../utils/firebaseIdentity.js';
import { isOrganizationSuspended } from '../utils/organizationStatus.js';
import { errorResponse, successResponse, tenantSuspendedResponse } from '../utils/response.js';

// Public invite endpoints (no sign-in): the invite page reads the invite, then
// accepting it creates the Firebase account and signs the person straight in.

const gone = (res: Response) =>
  void errorResponse(res, 'This invite has expired or was already used. Ask your admin for a new invite.', 410);

function isFirebaseAdminError(error: unknown): error is { code: string } {
  return typeof error === 'object' && error !== null && 'code' in error;
}

// The same page also opens the tenant admin invite sent from Super Admin
// (organization_invites), so a token that isn't an employee invite is tried there.
async function getOrganizationInvite(token: string, res: Response) {
  const invite = await organizationInviteModel.findInviteByToken(token);
  if (!invite || !organizationInviteModel.isInviteUsable(invite)) return gone(res);
  if (isOrganizationSuspended(invite.organization)) return void tenantSuspendedResponse(res, invite.organization.name);
  return successResponse(res, 'Invite fetched successfully', {
    email: invite.email,
    name: invite.name,
    organizationName: invite.organization.name,
    invitedByName: 'BlenAxis',
    expiresAt: invite.expiresAt.toISOString(),
    roles: ['Organization Admin'],
    isOrganizationAdmin: true,
  });
}

async function acceptOrganizationInvite(token: string, req: Request, res: Response) {
  const invite = await organizationInviteModel.findInviteByToken(token);
  if (!invite || !organizationInviteModel.isInviteUsable(invite)) return gone(res);
  if (isOrganizationSuspended(invite.organization)) return void tenantSuspendedResponse(res, invite.organization.name);
  if (await prisma.user.findUnique({ where: { email: invite.email } })) {
    return void errorResponse(res, 'This email already has a BlenAxis account. Ask support to make it the organization admin.', 409);
  }
  const { name, password } = req.body;

  const firebaseUser = await createFirebaseAccount(res, invite.email, password, name);
  if (!firebaseUser) return;
  const user = await organizationInviteModel.acceptOrganizationInvite(invite, firebaseUser.uid, name);
  return signInAfterAccept(res, user.id, invite.email, password);
}

async function createFirebaseAccount(res: Response, email: string, password: string, name: string) {
  try {
    return await firebaseAuth.createUser({ email, password, displayName: name, emailVerified: true });
  } catch (error) {
    if (isFirebaseAdminError(error) && error.code === 'auth/email-already-exists') {
      errorResponse(res, 'This email already has a BlenAxis sign-in. Ask support to link it to your organization.', 409);
      return null;
    }
    throw error;
  }
}

// Sign in straight away so the invite page lands on the dashboard.
async function signInAfterAccept(res: Response, userId: number, email: string, password: string) {
  let session;
  try {
    session = await signInWithPassword(email, password);
  } catch (error) {
    if (error instanceof FirebaseRestError) {
      logger.error('Invite accepted but auto sign-in failed', { code: error.code });
      return successResponse(res, 'Account created. Please sign in.', null, 201);
    }
    throw error;
  }
  res.set('X-Id-Token', session.idToken);
  res.set('X-Refresh-Token', session.refreshToken);
  return successResponse(res, 'Welcome to BlenAxis', await buildSession((await findSessionUser(userId))!));
}

async function getInvite(req: Request, res: Response, next: NextFunction) {
  try {
    const user = await inviteModel.findInvite(String(req.params.token));
    if (!user) return void (await getOrganizationInvite(String(req.params.token), res));
    if (!user || !user.organization || !inviteModel.isInviteUsable(user)) return gone(res);
    if (isOrganizationSuspended(user.organization)) return void tenantSuspendedResponse(res, user.organization.name);
    return successResponse(res, 'Invite fetched successfully', {
      email: user.email,
      name: user.name,
      organizationName: user.organization.name,
      invitedByName: user.invitedBy?.name ?? 'Your administrator',
      expiresAt: inviteModel.inviteExpiry(user.invitedAt!).toISOString(),
      roles: user.tenantRoles.map(({ role }) => roleLabel(role)),
      isOrganizationAdmin: user.tenantRoles.some(({ role }) => role.name === ORG_ADMIN_ROLE),
    });
  } catch (error) {
    next(error);
    return;
  }
}

async function acceptInvite(req: Request, res: Response, next: NextFunction) {
  try {
    const user = await inviteModel.findInvite(String(req.params.token));
    if (!user) return void (await acceptOrganizationInvite(String(req.params.token), req, res));
    if (!user.organization || !inviteModel.isInviteUsable(user)) return gone(res);
    if (isOrganizationSuspended(user.organization)) return void tenantSuspendedResponse(res, user.organization.name);
    const { name, password } = req.body;

    const firebaseUser = await createFirebaseAccount(res, user.email, password, name);
    if (!firebaseUser) return;
    await inviteModel.acceptInvite(user.id, firebaseUser.uid, name);
    return void (await signInAfterAccept(res, user.id, user.email, password));
  } catch (error) {
    next(error);
    return;
  }
}

export { getInvite, acceptInvite };
