/**
 * Authentication Routes
 *
 * User registration, login, email verification, and password reset
 */

import { Router, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import { hashPassword, verifyPassword, generateToken } from '../utils/encryption';
import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from '../auth/jwtService';
import { logger } from '../utils/logger';
import { validate } from '../middleware/validator';
import { sendVerificationEmail, sendPasswordResetEmail } from '../services/emailService';
import { publicRateLimiter, strictRateLimiter } from '../middleware/rateLimiter';

const router = Router();
const prisma = new PrismaClient();

// Validation schemas
const registerSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string(),
});

const verifyEmailSchema = z.object({
  token: z.string(),
});

const forgotPasswordSchema = z.object({
  email: z.string().email(),
});

const resetPasswordSchema = z.object({
  token: z.string(),
  password: z.string().min(8),
});

const refreshTokenSchema = z.object({
  refreshToken: z.string(),
});

/**
 * POST /register
 * Register new user
 */
router.post('/register', publicRateLimiter, validate(registerSchema), async (req: Request, res: Response) => {
  try {
    const { email, password } = req.body;

    // Check if user already exists
    const existingUser = await prisma.user.findUnique({
      where: { email },
    });

    if (existingUser) {
      return res.status(400).json({
        error: {
          code: 'USER_EXISTS',
          message: 'A user with this email already exists',
        },
      });
    }

    // Hash password
    const passwordHash = await hashPassword(password);

    // Generate email verification token
    const emailVerificationToken = generateToken();

    // Create user
    const user = await prisma.user.create({
      data: {
        email,
        password_hash: passwordHash,
        email_verification_token: emailVerificationToken,
        status: 'active',
      },
    });

    logger.info('User registered', { userId: user.id, email: user.email });

    // Send verification email
    try {
      await sendVerificationEmail(user.email, emailVerificationToken);
    } catch (error) {
      logger.error('Failed to send verification email', { error, userId: user.id, email: user.email });
      // Don't fail registration if email fails
    }

    // Generate JWT tokens so onboarding flows can proceed without a separate login call
    const accessToken = generateAccessToken(
      user.id,
      user.email,
      'starter' as any,
      'active' as any
    );
    const refreshToken = generateRefreshToken(user.id);

    return res.status(201).json({
      message: 'User registered successfully. Please check your email to verify your account.',
      userId: user.id,
      accessToken,
      refreshToken,
    });
  } catch (error) {
    logger.error('Registration error', { error });
    return res.status(500).json({
      error: {
        code: 'REGISTRATION_FAILED',
        message: 'Failed to register user',
      },
    });
  }
});

/**
 * POST /login
 * User login
 */
router.post('/login', publicRateLimiter, validate(loginSchema), async (req: Request, res: Response) => {
  try {
    const { email, password } = req.body;

    // Find user
    const user = await prisma.user.findUnique({
      where: { email },
      include: {
        subscriptions: true,
      },
    });

    if (!user) {
      return res.status(401).json({
        error: {
          code: 'INVALID_CREDENTIALS',
          message: 'Invalid email or password',
        },
      });
    }

    // Verify password
    const isValidPassword = await verifyPassword(password, user.password_hash);

    if (!isValidPassword) {
      return res.status(401).json({
        error: {
          code: 'INVALID_CREDENTIALS',
          message: 'Invalid email or password',
        },
      });
    }

    // Check account status
    if (user.status !== 'active') {
      return res.status(403).json({
        error: {
          code: 'ACCOUNT_SUSPENDED',
          message: 'Your account has been suspended',
        },
      });
    }

    // Pick the first active subscription for JWT claims (best-effort)
    const activeSub = user.subscriptions.find(s => s.status === 'active') || user.subscriptions[0];

    // Generate tokens
    const accessToken = generateAccessToken(
      user.id,
      user.email,
      activeSub?.plan_tier || 'starter' as any,
      activeSub?.status || 'active' as any
    );

    const refreshToken = generateRefreshToken(user.id);

    logger.info('User logged in', { userId: user.id, email: user.email });

    return res.json({
      accessToken,
      refreshToken,
      user: {
        id: user.id,
        email: user.email,
        email_verified: user.email_verified,
        plan: activeSub?.plan_tier || 'starter',
        subscriptionStatus: activeSub?.status || 'active',
      },
      subscriptions: user.subscriptions.map(s => ({
        plugin: s.plugin,
        plan_tier: s.plan_tier,
        status: s.status,
      })),
    });
  } catch (error) {
    logger.error('Login error', { error });
    return res.status(500).json({
      error: {
        code: 'LOGIN_FAILED',
        message: 'Failed to login',
      },
    });
  }
});

/**
 * POST /refresh
 * Refresh access token
 */
router.post('/refresh', validate(refreshTokenSchema), async (req: Request, res: Response) => {
  try {
    const { refreshToken } = req.body;

    // Verify refresh token
    const payload = verifyRefreshToken(refreshToken);

    // Get user
    const user = await prisma.user.findUnique({
      where: { id: payload.userId },
      include: {
        subscriptions: true,
      },
    });

    if (!user || user.status !== 'active') {
      return res.status(401).json({
        error: {
          code: 'INVALID_TOKEN',
          message: 'Invalid or expired refresh token',
        },
      });
    }

    // Pick the first active subscription for JWT claims
    const activeSub = user.subscriptions.find(s => s.status === 'active') || user.subscriptions[0];

    // Generate new access token
    const accessToken = generateAccessToken(
      user.id,
      user.email,
      activeSub?.plan_tier || 'starter' as any,
      activeSub?.status || 'active' as any
    );

    return res.json({
      accessToken,
    });
  } catch (error: any) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({
        error: {
          code: 'TOKEN_EXPIRED',
          message: 'Refresh token has expired. Please login again.',
        },
      });
    }

    logger.error('Token refresh error', { error });
    return res.status(500).json({
      error: {
        code: 'TOKEN_REFRESH_FAILED',
        message: 'Failed to refresh token',
      },
    });
  }
});

/**
 * POST /verify-email
 * Verify email address
 */
router.post('/verify-email', validate(verifyEmailSchema), async (req: Request, res: Response) => {
  try {
    const { token } = req.body;

    // Find user with token
    const user = await prisma.user.findFirst({
      where: {
        email_verification_token: token,
      },
    });

    if (!user) {
      return res.status(400).json({
        error: {
          code: 'INVALID_TOKEN',
          message: 'Invalid or expired verification token',
        },
      });
    }

    // Update user
    await prisma.user.update({
      where: { id: user.id },
      data: {
        email_verified: true,
        email_verification_token: null,
      },
    });

    logger.info('Email verified', { userId: user.id, email: user.email });

    return res.json({
      message: 'Email verified successfully',
    });
  } catch (error) {
    logger.error('Email verification error', { error });
    return res.status(500).json({
      error: {
        code: 'VERIFICATION_FAILED',
        message: 'Failed to verify email',
      },
    });
  }
});

/**
 * POST /forgot-password
 * Request password reset
 */
router.post('/forgot-password', strictRateLimiter, validate(forgotPasswordSchema), async (req: Request, res: Response) => {
  try {
    const { email } = req.body;

    // Find user
    const user = await prisma.user.findUnique({
      where: { email },
    });

    if (!user) {
      // Don't reveal if user exists
      return res.json({
        message: 'If a user with that email exists, a password reset link has been sent.',
      });
    }

    // Generate reset token
    const resetToken = generateToken();
    const resetExpires = new Date(Date.now() + 3600000); // 1 hour

    // Update user
    await prisma.user.update({
      where: { id: user.id },
      data: {
        password_reset_token: resetToken,
        password_reset_expires: resetExpires,
      },
    });

    logger.info('Password reset requested', { userId: user.id, email: user.email });

    // Send password reset email
    try {
      await sendPasswordResetEmail(user.email, resetToken);
    } catch (error) {
      logger.error('Failed to send password reset email', { error, userId: user.id, email: user.email });
      // Don't fail the request if email fails (security: don't reveal if user exists)
    }

    return res.json({
      message: 'If a user with that email exists, a password reset link has been sent.',
    });
  } catch (error) {
    logger.error('Forgot password error', { error });
    return res.status(500).json({
      error: {
        code: 'FORGOT_PASSWORD_FAILED',
        message: 'Failed to process password reset request',
      },
    });
  }
});

/**
 * POST /reset-password
 * Reset password with token
 */
router.post('/reset-password', publicRateLimiter, validate(resetPasswordSchema), async (req: Request, res: Response) => {
  try {
    const { token, password } = req.body;

    // Find user with valid token
    const user = await prisma.user.findFirst({
      where: {
        password_reset_token: token,
        password_reset_expires: {
          gte: new Date(),
        },
      },
    });

    if (!user) {
      return res.status(400).json({
        error: {
          code: 'INVALID_TOKEN',
          message: 'Invalid or expired reset token',
        },
      });
    }

    // Hash new password
    const passwordHash = await hashPassword(password);

    // Update user
    await prisma.user.update({
      where: { id: user.id },
      data: {
        password_hash: passwordHash,
        password_reset_token: null,
        password_reset_expires: null,
      },
    });

    logger.info('Password reset successfully', { userId: user.id, email: user.email });

    return res.json({
      message: 'Password reset successfully',
    });
  } catch (error) {
    logger.error('Password reset error', { error });
    return res.status(500).json({
      error: {
        code: 'PASSWORD_RESET_FAILED',
        message: 'Failed to reset password',
      },
    });
  }
});

export default router;
