import { AuthProvider, AuthProviderState } from "./AuthProvider";
import prisma from "@/prisma";

export type DiscordAccessTokenResponse = {
  access_token: string;
  token_type: string;
  expires_in: number;
  refresh_token: string;
  scope: string;
};

type DiscordUserResponse = {
  id: string;
  avatar: string | null;
  username: string;
  email: string | null;
  discriminator: string;
};

type DiscordGuildResponse = {
  id: string;
};

export class DiscordAuthProvider extends AuthProvider {
  private async getMe(): Promise<DiscordUserResponse> {
    const req = await fetch("https://discord.com/api/users/@me", {
      headers: {
        Authorization: `Bearer ${this.accessToken}`
      }
    });
    const res: DiscordUserResponse = await req.json();
    return res;
  }

  async isPermitted(): Promise<boolean> {
    const req = await fetch("https://discord.com/api/users/@me/guilds", {
      headers: {
        Authorization: `Bearer ${this.accessToken}`
      }
    });

    const res: DiscordGuildResponse[] = await req.json();
    const guilds = res.map((guild) => guild.id);
    const allowedGuilds = process.env.DISCORD_ALLOWED_GUILDS?.split(",") ?? [];

    let allowed = false;
    for (const guild of allowedGuilds) {
      if (guilds.includes(guild)) allowed = true;
    }
    return allowed;
  }

  async getDisplayName(): Promise<string> {
    const me = await this.getMe();
    return me.username;
  }

  async getUsername(): Promise<string> {
    const me = await this.getMe();
    return me.username + "#" + me.discriminator;
  }

  async getId(): Promise<string> {
    const me = await this.getMe();
    return me.id;
  }

  async getAvatar(): Promise<string | null> {
    const me = await this.getMe();
    return me.avatar !== null
      ? `https://cdn.discordapp.com/avatars/${me.id}/${me.avatar}.png?size=1024`
      : null;
  }

  async getEmail(): Promise<string | null> {
    const me = await this.getMe();
    return me.email;
  }

  async getState(): Promise<AuthProviderState> {
    const username = await this.getUsername();
    const id = await this.getId();

    return {
      name: "Discord",
      connected: true,
      id,
      username
    };
  }

  static get redirectUri(): string {
    return `${process.env.BASE_DOMAIN}oauth/discord/redirect`;
  }

  static async getToken(
    code: string
  ): Promise<DiscordAccessTokenResponse | null> {
    const form = new URLSearchParams();
    form.append("client_id", process.env.DISCORD_CLIENT_ID);
    form.append("client_secret", process.env.DISCORD_CLIENT_SECRET);
    form.append("grant_type", "authorization_code");
    form.append("code", code);
    form.append("redirect_uri", this.redirectUri);

    const tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: form.toString()
    });

    if (!tokenResponse.ok) return null;
    return await tokenResponse.json();
  }

  static async refreshToken(
    refreshToken: string
  ): Promise<DiscordAccessTokenResponse | null> {
    const req = await fetch("https://discord.com/api/oauth2/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams({
        client_id: process.env.DISCORD_CLIENT_ID,
        client_secret: process.env.DISCORD_CLIENT_SECRET,
        grant_type: "refresh_token",
        refresh_token: refreshToken
      }).toString()
    });

    if (!req.ok) return null;
    return await req.json();
  }

  static async update(
    id: string,
    accessToken: string,
    refreshToken: string,
    expiresAt: Date,
    userId?: number
  ): Promise<number> {
    const a = await prisma.discordAuth.upsert({
      where: {
        id
      },
      create: {
        id,
        accessToken,
        refreshToken,
        expiresAt,
        user:
          userId != null
            ? { connect: { id: userId } }
            : { create: { username: null } },
        invalid: false
      },
      update: {
        accessToken,
        refreshToken,
        expiresAt,
        invalid: false
      }
    });

    return a.userId;
  }
}
