// src-ssr/routes/auth.js
import express from 'express';
import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers'; // Ensure this is imported if not already
import prisma from '../database.js';
import { rpID, rpName, origin, challengeStore } from '../server.js'; // Import RP details and challenge store
import { getSetting } from '../utils/settings.js';

const router = express.Router();

// Helper function to get user authenticators
async function getUserAuthenticators(userId)
{
  return prisma.authenticator.findMany({
    where: { userId },
    select: {
      credentialID: true,
      credentialPublicKey: true,
      counter: true,
      transports: true,
    },
  });
}

// Helper function to get a user by username
export async function getUserByUsername(username)
{
  return prisma.user.findUnique({ where: { username } });
}

// Helper function to get a user by ID
export async function getUserById(id)
{
  return prisma.user.findUnique({ where: { id } });
}

// Helper function to get an authenticator by credential ID
async function getAuthenticatorByCredentialID(credentialID)
{
  return prisma.authenticator.findUnique({ where: { credentialID } });
}


// Generate Registration Options
router.post('/generate-registration-options', async(req, res) =>
{
  // Destructure username, email, and fullName from the request body
  const { username, email, fullName, registrationToken } = req.body;

  if (!username)
  {
    return res.status(400).json({ error: 'Username is required' });
  }

  //Check if the registrationToken matches the setting
  const registrationTokenSetting = await getSetting('REGISTRATION_TOKEN');

  if (registrationTokenSetting !== registrationToken && !req.session.loggedInUserId)
  {
    return res.status(403).json({ error: 'Invalid registration token' });
  }

  try
  {
    let user = await getUserByUsername(username);

    // If user doesn't exist, create one with the provided details
    if (!user)
    {
      const userData = { username };
      if (email) userData.email = email; // Add email if provided
      if (fullName) userData.fullName = fullName; // Add fullName if provided

      user = await prisma.user.create({
        data: userData,
      });
    }

    const userAuthenticators = await getUserAuthenticators(user.id);

    if(userAuthenticators.length > 0)
    {
      //The user is trying to register a new authenticator, so we need to check if the user registering is the same as the one in the session
      if (!req.session.loggedInUserId || req.session.loggedInUserId !== user.id)
      {
        return res.status(403).json({ error: 'Invalid registration attempt.' });
      }
    }

    const options = await generateRegistrationOptions({
      rpName,
      rpID,
      userName: user.username,
      // Don't prompt users for additional authenticators if they've already registered some
      excludeCredentials: userAuthenticators.map(auth => ({
        id: auth.credentialID, // Use isoBase64URL helper
        type: 'public-key',
        // Optional: Specify transports if you know them
        transports: auth.transports ? auth.transports.split(',') : undefined,
      })),
      authenticatorSelection: {
        // Defaults
        residentKey: 'required',
        userVerification: 'preferred',
      },
      // Strong advice: Always require attestation for registration
      attestationType: 'none', // Use 'none' for simplicity, 'direct' or 'indirect' recommended for production
    });

    // Store the challenge
    challengeStore.set(user.id, options.challenge);
    req.session.userId = user.id; // Temporarily store userId in session for verification step

    res.json(options);
  }
  catch (error)
  {
    console.error('Registration options error:', error);
    // Handle potential Prisma unique constraint errors (e.g., email already exists)
    if (error.code === 'P2002' && error.meta?.target?.includes('email'))
    {
      return res.status(409).json({ error: 'Email address is already in use.' });
    }
    res.status(500).json({ error: 'Failed to generate registration options' });
  }
});

// Verify Registration
router.post('/verify-registration', async(req, res) =>
{
  const { registrationResponse } = req.body;
  const userId = req.session.userId; // Retrieve userId stored during options generation

  if (!userId)
  {
    return res.status(400).json({ error: 'User session not found. Please start registration again.' });
  }

  const expectedChallenge = challengeStore.get(userId);

  if (!expectedChallenge)
  {
    return res.status(400).json({ error: 'Challenge not found or expired' });
  }

  try
  {
    const user = await getUserById(userId);
    if (!user)
    {
      return res.status(404).json({ error: 'User not found' });
    }

    const verification = await verifyRegistrationResponse({
      response: registrationResponse,
      expectedChallenge: expectedChallenge,
      expectedOrigin: origin,
      expectedRPID: rpID,
      requireUserVerification: false, // Adjust based on your requirements
    });

    const { verified, registrationInfo } = verification;

    if (verified && registrationInfo)
    {
      const { credential, credentialDeviceType, credentialBackedUp } = registrationInfo;

      const credentialID = credential.id;
      const credentialPublicKey = credential.publicKey;
      const counter = credential.counter;
      const transports = credential.transports || []; // Use empty array if transports are not provided

      // Check if authenticator with this ID already exists
      const existingAuthenticator = await getAuthenticatorByCredentialID(isoBase64URL.fromBuffer(credentialID));

      if (existingAuthenticator)
      {
        return res.status(409).json({ error: 'Authenticator already registered' });
      }

      // Save the authenticator
      await prisma.authenticator.create({
        data: {
          credentialID, // Store as Base64URL string
          credentialPublicKey: Buffer.from(credentialPublicKey), // Store as Bytes
          counter: BigInt(counter), // Store as BigInt
          credentialDeviceType,
          credentialBackedUp,
          transports: transports.join(','), // Store transports as comma-separated string
          userId: user.id,
        },
      });

      // Clear the challenge and temporary userId
      challengeStore.delete(userId);
      delete req.session.userId;

      res.json({ verified: true });
    }
    else
    {
      // This else block was previously misplaced before the if block
      res.status(400).json({ error: 'Registration verification failed' });
    }
  }
  catch (error)
  {
    console.error('Registration verification error:', error);
    // Handle potential Prisma unique constraint errors (e.g., email already exists)
    if (error.code === 'P2002' && error.meta?.target?.includes('email'))
    {
      return res.status(409).json({ error: 'Email address is already in use.' });
    }
    challengeStore.delete(userId); // Clean up challenge on error
    delete req.session.userId;
    res.status(500).json({ error: 'Failed to verify registration', details: error.message });
  }
});

// Generate Authentication Options
router.post('/generate-authentication-options', async(req, res) =>
{
  const { username } = req.body;

  try
  {
    let user;
    if (username)
    {
      user = await getUserByUsername(username);
    }
    else if (req.session.loggedInUserId)
    {
      // If already logged in, allow re-authentication (e.g., for step-up)
      user = await getUserById(req.session.loggedInUserId);
    }

    // if (!user)
    // {
    //   return res.status(404).json({ error: 'User not found' });
    // }

    // const userAuthenticators = await getUserAuthenticators(user.id);

    const options = await generateAuthenticationOptions({
      rpID,
      // Require users to use a previously-registered authenticator
      // allowCredentials: userAuthenticators.map(auth => ({
      //   id: auth.credentialID,
      //   type: 'public-key',
      //   transports: auth.transports ? auth.transports.split(',') : undefined,
      // })),
      allowCredentials: [],
      userVerification: 'preferred',
    });

    //Store challenge associated with random ID
    const challengeId = crypto.randomUUID();
    challengeStore.set(challengeId, options.challenge);
    req.session.challengeUserId = challengeId; // Store user ID associated with this challenge

    res.json(options);
  }
  catch (error)
  {
    console.error('Authentication options error:', error);
    res.status(500).json({ error: 'Failed to generate authentication options' });
  }
});

// Verify Authentication
router.post('/verify-authentication', async(req, res) =>
{
  const { authenticationResponse } = req.body;
  const challengeUserId = req.session.challengeUserId; // Get user ID associated with the challenge

  if (!challengeUserId)
  {
    return res.status(400).json({ error: 'Challenge session not found. Please try logging in again.' });
  }

  const expectedChallenge = challengeStore.get(challengeUserId);

  if (!expectedChallenge)
  {
    return res.status(400).json({ error: 'Challenge not found or expired' });
  }

  try
  {
  //   const user = await getUserById(challengeUserId);
  //   if (!user)
  //   {
  //     return res.status(404).json({ error: 'User associated with challenge not found' });
  //   }

    const authenticator = await getAuthenticatorByCredentialID(authenticationResponse.id);

    if (!authenticator)
    {
      return res.status(404).json({ error: 'Authenticator not found' });
    }

    const user = await getUserById(authenticator.userId);
    if (!user)
    {
      return res.status(404).json({ error: 'User not found' });
    }

    // Ensure the authenticator belongs to the user attempting to log in
    if (authenticator.userId !== user.id)
    {
      return res.status(403).json({ error: 'Authenticator does not belong to this user' });
    }

    const verification = await verifyAuthenticationResponse({
      response: authenticationResponse,
      expectedChallenge: expectedChallenge,
      expectedOrigin: origin,
      expectedRPID: rpID,
      credential: {
        id: authenticator.credentialID,
        publicKey: authenticator.credentialPublicKey,
        counter: authenticator.counter.toString(), // Convert BigInt to string for comparison
        transports: authenticator.transports ? authenticator.transports.split(',') : undefined,
      },
      requireUserVerification: false, // Enforce user verification
    });

    const { verified, authenticationInfo } = verification;

    if (verified)
    {
      // Update the authenticator counter
      await prisma.authenticator.update({
        where: { credentialID: authenticator.credentialID },
        data: { counter: BigInt(authenticationInfo.newCounter) }, // Update with the new counter
      });

      // Clear the challenge and associated user ID
      challengeStore.delete(challengeUserId);
      delete req.session.challengeUserId;

      // Log the user in
      req.session.loggedInUserId = user.id;

      res.json({ verified: true, user: { id: user.id, username: user.username } });
    }
    else
    {
      res.status(400).json({ error: 'Authentication verification failed' });
    }
  }
  catch (error)
  {
    console.error('Authentication verification error:', error);
    challengeStore.delete(challengeUserId); // Clean up challenge on error
    delete req.session.challengeUserId;
    res.status(500).json({ error: 'Failed to verify authentication', details: error.message });
  }
});

// GET Passkeys for Logged-in User
router.get('/passkeys', async(req, res) =>
{
  if (!req.session.loggedInUserId)
  {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  try
  {
    const userId = req.session.loggedInUserId;
    const authenticators = await prisma.authenticator.findMany({
      where: { userId },
      select: {
        credentialID: true, // Already Base64URL string
        // Add other fields if needed, e.g., createdAt if you add it to the schema
        // createdAt: true,
      },
    });

    // No need to convert credentialID here as it's stored as Base64URL string
    res.json(authenticators);
  }
  catch (error)
  {
    console.error('Error fetching passkeys:', error);
    res.status(500).json({ error: 'Failed to fetch passkeys' });
  }
});

// DELETE Passkey
router.delete('/passkeys/:credentialID', async(req, res) =>
{
  if (!req.session.loggedInUserId)
  {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  const { credentialID } = req.params; // This is already a Base64URL string from the client

  if (!credentialID)
  {
    return res.status(400).json({ error: 'Credential ID is required' });
  }

  try
  {
    const userId = req.session.loggedInUserId;

    // Find the authenticator first to ensure it belongs to the logged-in user
    const authenticator = await prisma.authenticator.findUnique({
      where: { credentialID: credentialID }, // Use the Base64URL string directly
    });

    if (!authenticator)
    {
      return res.status(404).json({ error: 'Passkey not found' });
    }

    // Security check: Ensure the passkey belongs to the user trying to delete it
    if (authenticator.userId !== userId)
    {
      return res.status(403).json({ error: 'Permission denied' });
    }

    // Delete the authenticator
    await prisma.authenticator.delete({
      where: { credentialID: credentialID },
    });

    res.json({ message: 'Passkey deleted successfully' });
  }
  catch (error)
  {
    console.error('Error deleting passkey:', error);
    // Handle potential Prisma errors, e.g., record not found if deleted between check and delete
    if (error.code === 'P2025')
    { // Prisma code for record not found on delete/update
      return res.status(404).json({ error: 'Passkey not found' });
    }
    res.status(500).json({ error: 'Failed to delete passkey' });
  }
});

// Check Authentication Status
router.get('/status', async(req, res) =>
{
  if (req.session.loggedInUserId)
  {
    const user = await getUserById(req.session.loggedInUserId);
    if (!user)
    {
      req.session.destroy(err =>
      {});
      return res.status(401).json({ status: 'unauthenticated' });
    }
    // Include email and fullName in the response
    return res.json({ status: 'authenticated', user: { id: user.id, username: user.username, email: user.email, fullName: user.fullName } });
  }
  res.json({ status: 'unauthenticated' });
});

// Logout
router.post('/logout', (req, res) =>
{
  req.session.destroy(err =>
  {
    if (err)
    {
      console.error('Logout error:', err);
      return res.status(500).json({ error: 'Failed to logout' });
    }
    res.json({ message: 'Logged out successfully' });
  });
});

export default router;