import passport from 'passport';
import { Strategy as JwtStrategy } from 'passport-jwt';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import type { Request } from 'express';
import config from '@/shared/config';
import db from '@/db/models';
import UsersDBApi from '@/db/api/users';
import cookies from '@/auth/cookies';

const providers = config.providers;

interface JwtPayload {
  user: { email: string };
}

type VerifyDone = (error: unknown, user?: unknown) => void;

// The social strategies' verify callback (compatible with passport-oauth2's
// `VerifyCallback`, which types `user` as `Express.User | false`).
type SocialDone = (error: unknown, user?: Express.User | false) => void;

passport.use(
  new JwtStrategy(
    {
      passReqToCallback: true,
      secretOrKey: config.secret_key,
      jwtFromRequest: cookies.extractAccessCookie,
    },
    async (req: Request, token: JwtPayload, done: VerifyDone) => {
      try {
        const user = await UsersDBApi.findBy({ email: token.user.email });

        if (user && user.disabled) {
          return done(new Error(`User '${user.email}' is disabled`));
        }

        req.currentUser = user ?? undefined;

        return done(null, user);
      } catch (error) {
        done(error);
      }
    },
  ),
);

function socialStrategy(
  email: string,
  _profile: unknown,
  provider: string,
  done: SocialDone,
): void {
  db.users
    .findOrCreate({ where: { email, provider } })
    .then(([user]) => done(null, { user }))
    .catch((error: unknown) => done(error));
}

if (config.google.clientId && config.google.clientSecret) {
  passport.use(
    new GoogleStrategy(
      {
        clientID: config.google.clientId,
        clientSecret: config.google.clientSecret,
        callbackURL: config.apiUrl + '/auth/signin/google/callback',
        passReqToCallback: true,
      },
      (_request, _accessToken, _refreshToken, profile, done) => {
        // passport-google-oauth20 exposes emails as a typed array (the scope
        // `email` is requested in the signin controller).
        const email = profile.emails?.[0]?.value ?? '';
        socialStrategy(email, profile, providers.GOOGLE, done);
      },
    ),
  );
}
