import { exportJWK, generateKeyPair } from 'jose';
import Provider from 'oidc-provider';

export enum OAuthClient {
  DEFAULT = 'client-default',
  RS256_TOKENS = 'client-RS256-tokens',
  RS256_PROFILE = 'client-RS256-profile',
}

export enum OAuthUser {
  NO_EMAIL = 'no-email',
  NO_NAME = 'no-name',
  ID_TOKEN_CLAIMS = 'id-token-claims',
  WITH_QUOTA = 'with-quota',
  WITH_USERNAME = 'with-username',
  WITH_ROLE = 'with-role',
}

const claims = [
  { sub: OAuthUser.NO_EMAIL },
  {
    sub: OAuthUser.NO_NAME,
    email: 'oauth-no-name@immich.app',
    email_verified: true,
  },
  {
    sub: OAuthUser.WITH_USERNAME,
    email: 'oauth-with-username@immich.app',
    email_verified: true,
    immich_username: 'user-username',
  },
  {
    sub: OAuthUser.WITH_QUOTA,
    email: 'oauth-with-quota@immich.app',
    email_verified: true,
    preferred_username: 'user-quota',
    immich_quota: 25,
  },
  {
    sub: OAuthUser.WITH_ROLE,
    email: 'oauth-with-role@immich.app',
    email_verified: true,
    immich_role: 'admin',
  },
];

const withDefaultClaims = (sub: string) => ({
  sub,
  email: `${sub}@immich.app`,
  name: 'OAuth User',
  given_name: `OAuth`,
  family_name: 'User',
  email_verified: true,
});

const getClaims = (sub: string, use?: string) => {
  if (sub === OAuthUser.ID_TOKEN_CLAIMS) {
    return {
      sub,
      email: `oauth-${sub}@immich.app`,
      email_verified: true,
      name: use === 'id_token' ? 'ID Token User' : 'Userinfo User',
    };
  }
  return claims.find((user) => user.sub === sub) || withDefaultClaims(sub);
};

const setup = async () => {
  const { privateKey, publicKey } = await generateKeyPair('RS256');

  const redirectUris = [
    'http://127.0.0.1:2285/auth/login',
    'https://photos.immich.app/oauth/mobile-redirect',
  ];
  const port = 2286;
  const host = '0.0.0.0';
  const oidc = new Provider(`http://${host}:${port}`, {
    renderError: async (ctx, out, error) => {
      console.error(out);
      console.error(error);
      ctx.body = 'Internal Server Error';
    },
    findAccount: (ctx, sub) => ({
      accountId: sub,
      claims: (use) => getClaims(sub, use),
    }),
    scopes: ['openid', 'email', 'profile'],
    claims: {
      openid: ['sub'],
      email: ['email', 'email_verified'],
      profile: [
        'name',
        'given_name',
        'family_name',
        'preferred_username',
        'immich_quota',
        'immich_username',
        'immich_role',
      ],
    },
    features: {
      jwtUserinfo: {
        enabled: true,
      },
    },
    cookies: {
      names: {
        session: 'oidc.session',
        interaction: 'oidc.interaction',
        resume: 'oidc.resume',
        state: 'oidc.state',
      },
    },
    conformIdTokenClaims: false,
    pkce: {
      required: () => false,
    },
    jwks: { keys: [await exportJWK(privateKey)] },
    clients: [
      {
        client_id: OAuthClient.DEFAULT,
        client_secret: OAuthClient.DEFAULT,
        redirect_uris: redirectUris,
        grant_types: ['authorization_code'],
        response_types: ['code'],
      },
      {
        client_id: OAuthClient.RS256_TOKENS,
        client_secret: OAuthClient.RS256_TOKENS,
        redirect_uris: redirectUris,
        grant_types: ['authorization_code'],
        id_token_signed_response_alg: 'RS256',
        jwks: { keys: [await exportJWK(publicKey)] },
      },
      {
        client_id: OAuthClient.RS256_PROFILE,
        client_secret: OAuthClient.RS256_PROFILE,
        redirect_uris: redirectUris,
        grant_types: ['authorization_code'],
        userinfo_signed_response_alg: 'RS256',
        jwks: { keys: [await exportJWK(publicKey)] },
      },
    ],
  });

  const onStart = () =>
    console.log(
      `[e2e-auth-server] http://${host}:${port}/.well-known/openid-configuration`,
    );
  const app = oidc.listen(port, host, onStart);
  return () => app.close();
};

export default setup;
