import { isRecord } from '@/shared/object';
import type { Request, Response, NextFunction } from 'express';
import passport from 'passport';
import config from '@/shared/config';
import { queryStr } from '@/api/http/request';
import AuthService from '@/services/auth';
import ForbiddenError from '@/shared/errors/forbidden';
import EmailSender from '@/services/email';
import cookies from '@/auth/cookies';

/** Extracts the minimal session user from an OAuth principal (`{ user }`). */
function readSessionUser(
  value: unknown,
): { id: string; email: string; organizationId: string | null } | null {
  if (!isRecord(value)) {
    return null;
  }
  const id = value.id;
  const email = value.email;
  const organizationId = value.organizationId;
  if (typeof id !== 'string' || typeof email !== 'string') {
    return null;
  }
  return {
    id,
    email,
    organizationId: typeof organizationId === 'string' ? organizationId : null,
  };
}

function refererUrl(req: Request): URL {
  const referer =
    req.headers.referer ||
    `${req.protocol}://${req.hostname}${req.originalUrl}`;
  return new URL(referer);
}

async function socialRedirect(
  req: Request,
  res: Response,
  principal: unknown,
): Promise<void> {
  const socialUser = isRecord(principal) ? principal.user : undefined;
  const sessionUser = readSessionUser(socialUser);
  if (!sessionUser) {
    throw new ForbiddenError();
  }
  const session = await AuthService.createSession(sessionUser, req);
  cookies.setSessionCookies(res, session);
  res.redirect(config.uiUrl);
}

export async function signinLocal(req: Request, res: Response): Promise<void> {
  const result = await AuthService.signin(req.body.email, req.body.password);
  const session = await AuthService.createSession(result.user, req);
  cookies.setSessionCookies(res, session);
  const payload = await AuthService.currentUserProfile(result.user);
  res.status(200).send(payload);
}

export async function refresh(req: Request, res: Response): Promise<void> {
  const session = await AuthService.refreshSession(
    cookies.extractRefreshCookie(req) ?? undefined,
    req,
  );
  cookies.setSessionCookies(res, session);
  const payload = await AuthService.currentUserProfile(session.user);
  res.status(200).send(payload);
}

export async function signout(req: Request, res: Response): Promise<void> {
  await AuthService.revokeSession(cookies.extractRefreshCookie(req) ?? undefined);
  cookies.clearSessionCookies(res);
  res.status(204).send();
}

export async function me(req: Request, res: Response): Promise<void> {
  if (!req.currentUser || !req.currentUser.id) {
    throw new ForbiddenError();
  }
  const payload = await AuthService.currentUserProfile(req.currentUser);
  res.status(200).send(payload);
}

export async function passwordReset(req: Request, res: Response): Promise<void> {
  const payload = await AuthService.passwordReset(
    req.body.token,
    req.body.password,
    req,
  );
  res.status(200).send(payload);
}

export async function passwordUpdate(
  req: Request,
  res: Response,
): Promise<void> {
  const payload = await AuthService.passwordUpdate(
    req.body.currentPassword,
    req.body.newPassword,
    req,
  );
  res.status(200).send(payload);
}

export async function sendEmailVerification(
  req: Request,
  res: Response,
): Promise<void> {
  if (!req.currentUser) {
    throw new ForbiddenError();
  }
  await AuthService.sendEmailAddressVerificationEmail(
    req.currentUser.email ?? '',
  );
  res.status(200).send(true);
}

export async function sendPasswordResetEmail(
  req: Request,
  res: Response,
): Promise<void> {
  const link = refererUrl(req);
  await AuthService.sendPasswordResetEmail(req.body.email, 'register', link.host);
  res.status(200).send(true);
}

export async function signup(req: Request, res: Response): Promise<void> {
  const link = refererUrl(req);
  const result = await AuthService.signup(
    req.body.email,
    req.body.password,
    req.body.organizationId,
    req,
    link.host,
  );
  const session = await AuthService.createSession(result.user, req);
  cookies.setSessionCookies(res, session);
  const payload = await AuthService.currentUserProfile(result.user);
  res.status(200).send(payload);
}

export async function updateProfile(
  req: Request,
  res: Response,
): Promise<void> {
  if (!req.currentUser || !req.currentUser.id) {
    throw new ForbiddenError();
  }
  await AuthService.updateProfile(req.body.profile, req.currentUser);
  res.status(200).send(true);
}

export async function verifyEmail(req: Request, res: Response): Promise<void> {
  const payload = await AuthService.verifyEmail(req.body.token, req);
  res.status(200).send(payload);
}

export function emailConfigured(_req: Request, res: Response): void {
  res.status(200).send(EmailSender.isConfigured);
}

export function googleSignin(
  req: Request,
  res: Response,
  next: NextFunction,
): void {
  passport.authenticate('google', {
    scope: ['profile', 'email'],
    state: queryStr(req.query.app),
  })(req, res, next);
}

export async function googleCallback(
  req: Request,
  res: Response,
): Promise<void> {
  await socialRedirect(req, res, req.user);
}
