import { FastifyInstance } from "fastify";
import { JwtPayload, User } from "@library/shared-types";
import { prisma } from "../lib/prisma";
import { randomBytes } from "crypto";

const ACCESS_TOKEN_EXPIRY = "15m";
const REFRESH_TOKEN_EXPIRY_DAYS = 7;

export class AuthService {
  private prisma = prisma;

  constructor(private readonly app: FastifyInstance) {}

  /**
   * Generate short-lived access token for user.
   */
  async generateToken(user: User): Promise<string> {
    const payload: JwtPayload = {
      sub: user.id,
      email: user.email,
      username: user.username,
      isAdmin: user.isAdmin,
    };

    return this.app.jwt.sign(payload, {
      expiresIn: ACCESS_TOKEN_EXPIRY,
    });
  }

  /**
   * Generate a secure refresh token and store it in the database.
   */
  async generateRefreshToken(userId: string): Promise<string> {
    const token = randomBytes(64).toString("hex");
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + REFRESH_TOKEN_EXPIRY_DAYS);

    await this.prisma.refreshToken.create({
      data: {
        token,
        userId,
        expiresAt,
      },
    });

    return token;
  }

  /**
   * Validate and consume a refresh token, returning the user if valid.
   */
  async validateRefreshToken(token: string): Promise<User | null> {
    const refreshToken = await this.prisma.refreshToken.findUnique({
      where: { token },
      include: { user: true },
    });

    if (!refreshToken) {
      return null;
    }

    if (refreshToken.expiresAt < new Date()) {
      await this.prisma.refreshToken.delete({ where: { id: refreshToken.id } });
      return null;
    }

    const dbUser = refreshToken.user;
    return {
      id: dbUser.id,
      discordId: dbUser.discordId,
      username: dbUser.username,
      email: dbUser.email,
      avatar: dbUser.avatar || undefined,
      slug: dbUser.slug || undefined,
      displayName: dbUser.displayName || undefined,
      bio: dbUser.bio || undefined,
      profilePublic: dbUser.profilePublic,
      website: dbUser.website || undefined,
      discordServer: dbUser.discordServer || undefined,
      bluesky: dbUser.bluesky || undefined,
      github: dbUser.github || undefined,
      linkedin: dbUser.linkedin || undefined,
      isAdmin: dbUser.isAdmin,
      isBanned: dbUser.isBanned,
      inDiscord: dbUser.inDiscord,
      isVip: dbUser.isVip,
      isMod: dbUser.isMod,
      isStaff: dbUser.isStaff,
    };
  }

  /**
   * Rotate a refresh token (invalidate old, create new with same expiry).
   * Preserves original expiry so users must re-auth with Discord every 7 days.
   */
  async rotateRefreshToken(oldToken: string, userId: string): Promise<string | null> {
    const existingToken = await this.prisma.refreshToken.findUnique({
      where: { token: oldToken },
    });

    if (!existingToken) {
      return null;
    }

    const originalExpiry = existingToken.expiresAt;

    await this.prisma.refreshToken.delete({
      where: { id: existingToken.id },
    });

    const newToken = randomBytes(64).toString("hex");

    await this.prisma.refreshToken.create({
      data: {
        token: newToken,
        userId,
        expiresAt: originalExpiry,
      },
    });

    return newToken;
  }

  /**
   * Revoke a specific refresh token.
   */
  async revokeRefreshToken(token: string): Promise<void> {
    await this.prisma.refreshToken.deleteMany({
      where: { token },
    });
  }

  /**
   * Revoke all refresh tokens for a user (logout from all devices).
   */
  async revokeAllUserTokens(userId: string): Promise<void> {
    await this.prisma.refreshToken.deleteMany({
      where: { userId },
    });
  }

  /**
   * Clean up expired refresh tokens (can be called periodically).
   */
  async cleanupExpiredTokens(): Promise<number> {
    const result = await this.prisma.refreshToken.deleteMany({
      where: {
        expiresAt: { lt: new Date() },
      },
    });
    return result.count;
  }

  /**
   * Verify JWT token.
   */
  async verifyToken(token: string): Promise<JwtPayload> {
    return this.app.jwt.verify(token) as JwtPayload;
  }

  /**
   * Get user by ID from database.
   */
  async getUserById(id: string): Promise<User | null> {
    const dbUser = await this.prisma.user.findUnique({
      where: { id },
    });

    if (!dbUser) {
      return null;
    }

    return {
      id: dbUser.id,
      discordId: dbUser.discordId,
      username: dbUser.username,
      email: dbUser.email,
      avatar: dbUser.avatar || undefined,
      slug: dbUser.slug || undefined,
      displayName: dbUser.displayName || undefined,
      bio: dbUser.bio || undefined,
      profilePublic: dbUser.profilePublic,
      website: dbUser.website || undefined,
      discordServer: dbUser.discordServer || undefined,
      bluesky: dbUser.bluesky || undefined,
      github: dbUser.github || undefined,
      linkedin: dbUser.linkedin || undefined,
      isAdmin: dbUser.isAdmin,
      isBanned: dbUser.isBanned,
      inDiscord: dbUser.inDiscord,
      isVip: dbUser.isVip,
      isMod: dbUser.isMod,
      isStaff: dbUser.isStaff,
    };
  }

  /**
   * Create or update user from Discord OAuth data.
   */
  async createOrUpdateUserFromDiscord(discordData: DiscordUser, inDiscord: boolean, isVip: boolean, isMod: boolean, isStaff: boolean): Promise<User> {
    const avatarUrl = discordData.avatar
      ? `https://cdn.discordapp.com/avatars/${discordData.id}/${discordData.avatar}.png`
      : undefined;

    // Upsert user in database
    const dbUser = await this.prisma.user.upsert({
      where: {
        discordId: discordData.id,
      },
      create: {
        discordId: discordData.id,
        username: discordData.username,
        email: discordData.email,
        avatar: avatarUrl,
        isAdmin: discordData.id === process.env.ADMIN_DISCORD_ID,
        inDiscord,
        isVip,
        isMod,
        isStaff,
      },
      update: {
        username: discordData.username,
        email: discordData.email,
        avatar: avatarUrl,
        inDiscord,
        isVip,
        isMod,
        isStaff,
      },
    });

    return {
      id: dbUser.id,
      discordId: dbUser.discordId,
      username: dbUser.username,
      email: dbUser.email,
      avatar: dbUser.avatar || undefined,
      slug: dbUser.slug || undefined,
      displayName: dbUser.displayName || undefined,
      bio: dbUser.bio || undefined,
      profilePublic: dbUser.profilePublic,
      website: dbUser.website || undefined,
      discordServer: dbUser.discordServer || undefined,
      bluesky: dbUser.bluesky || undefined,
      github: dbUser.github || undefined,
      linkedin: dbUser.linkedin || undefined,
      isAdmin: dbUser.isAdmin,
      isBanned: dbUser.isBanned,
      inDiscord: dbUser.inDiscord,
      isVip: dbUser.isVip,
      isMod: dbUser.isMod,
      isStaff: dbUser.isStaff,
    };
  }
}

interface DiscordUser {
  id: string;
  username: string;
  email: string;
  avatar?: string;
}