import { FastifyPluginAsync } from "fastify";
import { AuthService } from "../../services/auth.service";
import { AuditService } from "../../services/audit.service";
import { AchievementService } from "../../services/achievement.service";
import { AuthResponse, AuditAction, AuditCategory, AchievementCategory } from "@library/shared-types";

const authRoutes: FastifyPluginAsync = async (app) => {
  const authService = new AuthService(app);

  /**
   * Discord OAuth callback.
   */
  app.get("/callback", async (request, reply) => {
    try {
      const tokenResult = await app.oauth2Discord.getAccessTokenFromAuthorizationCodeFlow(
        request
      );

      // Get user data from Discord API
      const discordResponse = await fetch("https://discord.com/api/users/@me", {
        headers: {
          Authorization: `Bearer ${tokenResult.token.access_token}`,
        },
      });

      if (!discordResponse.ok) {
        throw new Error("Failed to fetch Discord user data");
      }

      const userData = await discordResponse.json();

      // Check if user is in our Discord server and has special roles
      let inDiscord = false;
      let isVip = false;
      let isMod = false;
      let isStaff = false;
      const guildId = process.env.DISCORD_GUILD_ID;
      const sponsorRoleId = process.env.SPONSOR_ROLE_ID;
      const modRoleId = process.env.MOD_ROLE_ID;
      const staffRoleId = process.env.STAFF_ROLE_ID;

      if (guildId) {
        const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", {
          headers: {
            Authorization: `Bearer ${tokenResult.token.access_token}`,
          },
        });

        if (guildsResponse.ok) {
          const guilds = await guildsResponse.json() as Array<{ id: string }>;
          inDiscord = guilds.some(guild => guild.id === guildId);
        }

        // If user is in Discord, check for special roles
        if (inDiscord) {
          const memberResponse = await fetch(
            `https://discord.com/api/users/@me/guilds/${guildId}/member`,
            {
              headers: {
                Authorization: `Bearer ${tokenResult.token.access_token}`,
              },
            }
          );

          if (memberResponse.ok) {
            const memberData = await memberResponse.json() as { roles: string[] };
            if (sponsorRoleId) {
              isVip = memberData.roles.includes(sponsorRoleId);
            }
            if (modRoleId) {
              isMod = memberData.roles.includes(modRoleId);
            }
            if (staffRoleId) {
              isStaff = memberData.roles.includes(staffRoleId);
            }
          }
        }
      }

      // Create or update user in database
      const user = await authService.createOrUpdateUserFromDiscord(userData, inDiscord, isVip, isMod, isStaff);

      // Generate access token and refresh token
      const accessToken = await authService.generateToken(user);
      const refreshToken = await authService.generateRefreshToken(user.id);

      // Log successful login
      await AuditService.log({
        action: AuditAction.login,
        category: AuditCategory.auth,
        userId: user.id,
        details: `User ${user.username} logged in via Discord`,
        success: true,
      }, request);

      // Update login streak and check engagement achievements
      const achievementService = new AchievementService();
      await achievementService.updateLoginStreak(user.id);
      await achievementService.checkAchievements(
        user.id,
        AchievementCategory.Engagement,
        request
      );

      // Assign library member role if user is in Discord server but doesn't have it
      const libraryRoleId = process.env.LIBRARY_ROLE_ID;
      if (inDiscord && guildId && libraryRoleId) {
        try {
          const memberResponse = await fetch(
            `https://discord.com/api/users/@me/guilds/${guildId}/member`,
            {
              headers: {
                Authorization: `Bearer ${tokenResult.token.access_token}`,
              },
            }
          );

          if (memberResponse.ok) {
            const memberData = await memberResponse.json() as { roles: string[] };
            const hasLibraryRole = memberData.roles.includes(libraryRoleId);

            if (!hasLibraryRole) {
              const botToken = process.env.DISCORD_BOT_TOKEN;
              if (botToken) {
                const assignRoleResponse = await fetch(
                  `https://discord.com/api/v10/guilds/${guildId}/members/${userData.id}/roles/${libraryRoleId}`,
                  {
                    method: "PUT",
                    headers: {
                      Authorization: `Bot ${botToken}`,
                      "Content-Type": "application/json",
                    },
                  }
                );

                if (assignRoleResponse.ok || assignRoleResponse.status === 204) {
                  app.log.info(`Assigned library role to user ${user.username} (${user.id})`);
                } else {
                  app.log.error(
                    `Failed to assign library role to user ${user.username}: ${assignRoleResponse.status}`
                  );
                }
              }
            }
          }
        } catch (error) {
          // Don't fail the login if role assignment fails
          app.log.error({ err: error }, "Error assigning library role");
        }
      }

      // Set signed cookies and redirect to frontend
      reply
        .setCookie("auth-token", accessToken, {
          path: "/",
          httpOnly: true,
          secure: process.env.NODE_ENV === "production",
          sameSite: "lax",
          maxAge: 15 * 60, // 15 minutes
          signed: true,
        })
        .setCookie("refresh-token", refreshToken, {
          path: "/api/auth",
          httpOnly: true,
          secure: process.env.NODE_ENV === "production",
          sameSite: "lax",
          maxAge: 7 * 24 * 60 * 60, // 7 days
          signed: true,
        })
        .redirect("/"); // Redirect to root since API serves frontend
    } catch (error) {
      // Log failed login attempt
      await AuditService.log({
        action: AuditAction.loginFailed,
        category: AuditCategory.security,
        details: error instanceof Error ? error.message : String(error),
        success: false,
      }, request);

      app.log.error({ err: error }, "Auth callback error");
      reply
        .code(401)
        .send({ error: "Authentication failed" });
    }
  });

  /**
   * Get current user.
   */
  app.get<{ Reply: AuthResponse | { error: string } }>(
    "/me",
    {
      preValidation: [app.authenticate],
    },
    async (request, reply) => {
      const jwtUser = request.user as { id: string };
      const user = await authService.getUserById(jwtUser.id);

      if (!user) {
        return reply.code(404).send({ error: "User not found" });
      }

      const token = await authService.generateToken(user);

      return {
        user,
        accessToken: token,
      };
    }
  );

  /**
   * Refresh access token using refresh token.
   */
  app.post("/refresh", async (request, reply) => {
    const signedRefreshToken = request.cookies["refresh-token"];
    if (!signedRefreshToken) {
      return reply.code(401).send({ error: "No refresh token provided" });
    }

    const unsignedResult = request.unsignCookie(signedRefreshToken);
    if (!unsignedResult.valid || !unsignedResult.value) {
      return reply.code(401).send({ error: "Invalid refresh token" });
    }

    const refreshToken = unsignedResult.value;
    const user = await authService.validateRefreshToken(refreshToken);

    if (!user) {
      reply.clearCookie("refresh-token", { path: "/api/auth", signed: true });
      return reply.code(401).send({ error: "Refresh token expired or invalid" });
    }

    if (user.isBanned) {
      await authService.revokeAllUserTokens(user.id);
      reply
        .clearCookie("auth-token", { path: "/", signed: true })
        .clearCookie("refresh-token", { path: "/api/auth", signed: true });
      return reply.code(403).send({ error: "Account is banned" });
    }

    // Rotate refresh token for security
    const newRefreshToken = await authService.rotateRefreshToken(refreshToken, user.id);
    if (!newRefreshToken) {
      return reply.code(401).send({ error: "Failed to rotate refresh token" });
    }

    const newAccessToken = await authService.generateToken(user);

    reply
      .setCookie("auth-token", newAccessToken, {
        path: "/",
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "lax",
        maxAge: 15 * 60, // 15 minutes
        signed: true,
      })
      .setCookie("refresh-token", newRefreshToken, {
        path: "/api/auth",
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "lax",
        maxAge: 7 * 24 * 60 * 60, // 7 days
        signed: true,
      })
      .send({ user, accessToken: newAccessToken });
  });

  /**
   * Logout.
   */
  app.post("/logout", async (request, reply) => {
    // Revoke refresh token if present
    const signedRefreshToken = request.cookies["refresh-token"];
    if (signedRefreshToken) {
      const unsignedResult = request.unsignCookie(signedRefreshToken);
      if (unsignedResult.valid && unsignedResult.value) {
        await authService.revokeRefreshToken(unsignedResult.value);
      }
    }

    // Try to get user ID from JWT for audit logging
    try {
      await request.jwtVerify();
      const user = request.user as { id?: string; username?: string };
      if (user?.id) {
        await AuditService.log({
          action: AuditAction.logout,
          category: AuditCategory.auth,
          userId: user.id,
          details: `User ${user.username ?? "unknown"} logged out`,
          success: true,
        }, request);
      }
    } catch {
      // User wasn't authenticated, just proceed with logout
    }

    reply
      .clearCookie("auth-token", {
        path: "/",
        signed: true,
      })
      .clearCookie("refresh-token", {
        path: "/api/auth",
        signed: true,
      })
      .send({ message: "Logged out successfully" });
  });

  /**
   * Get CSRF token for state-changing requests.
   */
  app.get("/csrf-token", async (request, reply) => {
    const token = reply.generateCsrf();
    return { csrfToken: token };
  });
};

export default authRoutes;