import { Router } from 'express';
import { OAuth2Client } from 'google-auth-library';
import appleSignin from 'apple-signin-auth';
import { authenticateUser, registerUser, findOrCreateGoogleUser, findOrCreateAppleUser, changePassword, verifyToken } from '../middleware/auth';
import { authLimiter } from '../middleware/rateLimit';
import { config } from '../config';
import { createLogger } from '../utils/logger';
import { UserCredentials } from '../types';
import { UserModel, UserPlan, BillingCycle } from '../models/User';

const router = Router();
const logger = createLogger('AuthRoutes');

/**
 * POST /auth/register
 * Register a new user
 */
router.post('/register', authLimiter, async (req, res) => {
  try {
    const credentials: UserCredentials = req.body;

    if (!credentials.email || !credentials.password) {
      res.status(400).json({ error: 'Email and password are required' });
      return;
    }

    await registerUser(credentials);

    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    logger.error('Registration failed', {
      error: error instanceof Error ? error.message : String(error),
    });

    if (error instanceof Error && error.message === 'User already exists') {
      res.status(409).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Registration failed' });
    }
  }
});

/**
 * POST /auth/login
 * Authenticate user and get JWT token
 */
router.post('/login', async (req, res) => {
  try {
    const credentials: UserCredentials = req.body;
    const ip = req.ip || 'unknown';

    if (!credentials.email || !credentials.password) {
      res.status(400).json({ error: 'Email and password are required' });
      return;
    }

    const token = await authenticateUser(credentials, ip);

    res.json({ token });
  } catch (error) {
    logger.error('Authentication failed', {
      error: error instanceof Error ? error.message : String(error),
      ip: req.ip,
    });

    res.status(401).json({
      error: error instanceof Error ? error.message : 'Authentication failed',
    });
  }
});

/**
 * POST /auth/google
 * Authenticate with Google ID token
 */
router.post('/google', authLimiter, async (req, res) => {
  try {
    if (!config.google.clientId) {
      res.status(501).json({ error: 'Google authentication is not configured' });
      return;
    }

    const { credential } = req.body;

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

    const client = new OAuth2Client(config.google.clientId);
    const ticket = await client.verifyIdToken({
      idToken: credential,
      audience: config.google.clientId,
    });

    const payload = ticket.getPayload();
    if (!payload || !payload.email) {
      res.status(401).json({ error: 'Invalid Google token' });
      return;
    }

    const token = await findOrCreateGoogleUser({
      email: payload.email,
      googleId: payload.sub,
      name: payload.name,
    });

    res.json({ token });
  } catch (error) {
    logger.error('Google authentication failed', {
      error: error instanceof Error ? error.message : String(error),
    });
    res.status(401).json({ error: 'Google authentication failed' });
  }
});

/**
 * POST /auth/apple
 * Authenticate with Apple identity token
 */
router.post('/apple', authLimiter, async (req, res) => {
  try {
    const appleClientId = process.env.APPLE_CLIENT_ID;
    if (!appleClientId) {
      res.status(501).json({ error: 'Apple authentication is not configured' });
      return;
    }

    const { identityToken } = req.body;
    if (!identityToken) {
      res.status(400).json({ error: 'Identity token is required' });
      return;
    }

    const payload = await appleSignin.verifyIdToken(identityToken, {
      audience: appleClientId,
      ignoreExpiration: false,
    });

    if (!payload.sub) {
      res.status(401).json({ error: 'Invalid Apple token' });
      return;
    }

    // Apple may use a private relay email; fall back to appleId-based email
    const email = payload.email || `apple_${payload.sub}@privaterelay.appleid.com`;

    const token = await findOrCreateAppleUser({ email, appleId: payload.sub });

    res.json({ token });
  } catch (error) {
    logger.error('Apple authentication failed', {
      error: error instanceof Error ? error.message : String(error),
    });
    res.status(401).json({ error: 'Apple authentication failed' });
  }
});

/**
 * POST /auth/verify
 * Verify JWT token validity
 */
router.post('/verify', async (req, res) => {
  const { token } = req.body;

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

  try {
    const jwt = require('jsonwebtoken');
    const { config } = require('../config');

    const decoded = jwt.verify(token, config.jwt.secret);

    res.json({ valid: true, user: decoded });
  } catch (error) {
    res.json({ valid: false });
  }
});

/**
 * POST /auth/change-password
 * Change password for authenticated user
 */
router.post('/change-password', verifyToken, async (req, res) => {
  try {
    const { currentPassword, newPassword } = req.body;
    const user = (req as any).user;

    if (!currentPassword || !newPassword) {
      res.status(400).json({ error: 'currentPassword and newPassword are required' });
      return;
    }

    if (newPassword.length < 8) {
      res.status(400).json({ error: 'New password must be at least 8 characters' });
      return;
    }

    await changePassword(user.email, currentPassword, newPassword);
    res.json({ message: 'Password updated successfully' });
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Failed to change password';
    res.status(400).json({ error: message });
  }
});

/**
 * GET /auth/me
 * Get current user profile including plan
 */
router.get('/me', verifyToken, async (req, res) => {
  try {
    const { email } = (req as any).user;
    const user = await UserModel.findOne({ email }).select('-passwordHash');
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }
    res.json({
      email: user.email,
      role: user.role,
      plan: user.plan,
      planBillingCycle: user.planBillingCycle,
      provider: user.provider,
      createdAt: user.createdAt,
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch user' });
  }
});

const VALID_PLANS: UserPlan[] = ['free', 'basic', 'premium', 'max', 'business'];
const VALID_BILLING_CYCLES: BillingCycle[] = ['monthly', 'yearly'];

/**
 * POST /auth/plan
 * Update the authenticated user's plan
 * Body: { plan: UserPlan, billingCycle?: BillingCycle }
 */
router.post('/plan', verifyToken, async (req, res) => {
  try {
    const { plan, billingCycle } = req.body as { plan: UserPlan; billingCycle?: BillingCycle };
    const { email } = (req as any).user;

    if (!plan || !VALID_PLANS.includes(plan)) {
      res.status(400).json({ error: `plan must be one of: ${VALID_PLANS.join(', ')}` });
      return;
    }
    if (billingCycle && !VALID_BILLING_CYCLES.includes(billingCycle)) {
      res.status(400).json({ error: `billingCycle must be monthly or yearly` });
      return;
    }

    const update: { plan: UserPlan; planBillingCycle?: BillingCycle } = { plan };
    if (billingCycle) update.planBillingCycle = billingCycle;

    const user = await UserModel.findOneAndUpdate({ email }, update, { new: true });
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Issue a fresh JWT with updated plan
    const jwt = require('jsonwebtoken');
    const token = jwt.sign(
      { userId: user.email, email: user.email, role: user.role, plan: user.plan },
      config.jwt.secret,
      { expiresIn: config.jwt.expiresIn }
    );

    logger.info('Plan updated', { email, plan, billingCycle });
    res.json({ token, plan: user.plan, planBillingCycle: user.planBillingCycle });
  } catch (error) {
    logger.error('Plan update failed', { error: error instanceof Error ? error.message : String(error) });
    res.status(500).json({ error: 'Failed to update plan' });
  }
});

export default router;
