Clone
import { Database } from '../lib/db';
import { createJWT } from '../middleware/auth';
import { validateEmail } from '../utils/validation';
import { ValidationError, AuthError } from '../utils/errors';

export async function authRoutes(request, env, ctx) {
  const url = new URL(request.url);
  const path = url.pathname;
  
  if (path === '/api/auth/request' && request.method === 'POST') {
    return requestMagicLink(request, env);
  }
  
  if (path === '/api/auth/verify' && request.method === 'POST') {
    return verifyMagicLink(request, env);
  }
  
  if (path === '/api/auth/logout' && request.method === 'POST') {
    return logout(request, env);
  }
  
  return new Response('Not Found', { status: 404 });
}

async function requestMagicLink(request, env) {
  try {
    const body = await request.json();
    const { email } = body;
    
    // Validate email
    validateEmail(email);
    
    const db = new Database(env.DB);
    
    // Check if email is authorized
    const authorized = await db.isEmailAuthorized(email);
    if (!authorized) {
      // Check environment variable whitelist as fallback
      const authorizedEmails = env.AUTHORIZED_EMAILS?.split(',').map(e => e.trim()) || [];
      if (!authorizedEmails.includes(email)) {
        return new Response(JSON.stringify({ 
          error: 'This email is not authorized to use this service' 
        }), {
          status: 403,
          headers: { 'Content-Type': 'application/json' }
        });
      }
      // Add to database for future
      await db.addAuthorizedEmail(email);
    }
    
    // Generate secure token
    const tokenBytes = new Uint8Array(32);
    crypto.getRandomValues(tokenBytes);
    const token = Array.from(tokenBytes, b => b.toString(16).padStart(2, '0')).join('');
    
    // Set expiry to 15 minutes from now
    const expiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString();
    
    // Store token in database
    await db.createAuthToken(token, email, expiresAt);
    
    // Send email (mock for now, integrate with email service)
    const magicLink = `${env.FRONTEND_URL}/auth/verify?token=${token}`;
    
    // TODO: Integrate with actual email service
    console.log(`Magic link for ${email}: ${magicLink}`);
    
    // In production, send actual email
    if (env.EMAIL_API_KEY) {
      await sendEmail(env, email, magicLink);
    }
    
    return new Response(JSON.stringify({ 
      success: true,
      message: 'Magic link sent to your email'
    }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Magic link request error:', error);
    return new Response(JSON.stringify({ 
      error: error.message || 'Failed to send magic link' 
    }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

async function verifyMagicLink(request, env) {
  try {
    const body = await request.json();
    const { token } = body;
    
    if (!token) {
      throw new ValidationError('Token is required');
    }
    
    const db = new Database(env.DB);
    
    // Get token from database
    const authToken = await db.getAuthToken(token);
    if (!authToken) {
      throw new AuthError('Invalid or expired token');
    }
    
    // Mark token as used
    await db.markTokenUsed(token);
    
    // Get or create user
    let user = await db.getUserByEmail(authToken.email);
    if (!user) {
      const userId = crypto.randomUUID();
      await db.createUser(userId, authToken.email);
      user = { id: userId, email: authToken.email };
    }
    
    // Create JWT
    const jwt = await createJWT({
      sub: user.id,
      email: user.email
    }, env.JWT_SECRET);
    
    return new Response(JSON.stringify({ 
      success: true,
      token: jwt,
      user: {
        id: user.id,
        email: user.email
      }
    }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    console.error('Magic link verification error:', error);
    return new Response(JSON.stringify({ 
      error: error.message || 'Failed to verify token' 
    }), {
      status: error.status || 400,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

async function logout(request, env) {
  // Since we're using JWTs, we can't invalidate them server-side
  // Client should remove the token
  return new Response(JSON.stringify({ 
    success: true,
    message: 'Logged out successfully'
  }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
}

async function sendEmail(env, email, magicLink) {
  // Postmark API integration
  const response = await fetch('https://api.postmarkapp.com/email', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'X-Postmark-Server-Token': env.EMAIL_API_KEY || env.POSTMARK_SERVER_TOKEN
    },
    body: JSON.stringify({
      From: env.EMAIL_FROM || 'noreply@qrurl.us',
      To: email,
      Subject: 'Your QRurl Login Link',
      HtmlBody: `
        <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
          <h2>Login to QRurl</h2>
          <p>Click the link below to log in to your QRurl account:</p>
          <a href="${magicLink}" style="display: inline-block; padding: 12px 24px; background: #000; color: #fff; text-decoration: none; border-radius: 4px;">
            Log In
          </a>
          <p style="color: #666; font-size: 14px; margin-top: 20px;">
            This link expires in 15 minutes. If you didn't request this, please ignore this email.
          </p>
        </div>
      `,
      TextBody: `Login to QRurl\n\nClick the link below to log in to your QRurl account:\n${magicLink}\n\nThis link expires in 15 minutes. If you didn't request this, please ignore this email.`,
      MessageStream: 'outbound'
    })
  });
  
  if (!response.ok) {
    const error = await response.text();
    console.error('Postmark error:', error);
    throw new Error('Failed to send email');
  }
  
  const result = await response.json();
  console.log('Email sent successfully:', result.MessageID);
}