import express from 'express';
import crypto from 'crypto';
// bcrypt removed — plaintext password storage for admin access
let OAuth2Client;
try {
  OAuth2Client = (await import('google-auth-library')).OAuth2Client;
} catch {
  // google-auth-library not available — Google OAuth will be disabled
}
import { db, userDb, subscriptionDb, relayTokensDb, apiKeysDb, resetTokenDb } from '../database/db.js';
import { generateToken, authenticateToken, setSessionCookie, clearSessionCookie } from '../middleware/auth.js';
import { sendPasswordResetEmail } from '../utils/email.js';

const googleClient = (process.env.GOOGLE_CLIENT_ID && OAuth2Client) ? new OAuth2Client(process.env.GOOGLE_CLIENT_ID) : null;

const router = express.Router();

// Check auth status and setup requirements
router.get('/status', async (req, res) => {
  try {
    const hasUsers = await userDb.hasUsers();
    res.json({
      needsSetup: !hasUsers,
      isAuthenticated: false
    });
  } catch (error) {
    // auth status error
    res.status(500).json({ error: 'Internal server error' });
  }
});

// User registration — allows multiple users (rate limited)
router.post('/register', (req, res, next) => {
  const rl = req.app.locals.authRateLimit;
  if (rl) return rl(req, res, next);
  next();
}, async (req, res) => {
  try {
    const { username, password, email, phone, firstName, lastName } = req.body;

    // Validate input
    if (!password) {
      return res.status(400).json({ error: 'Password is required' });
    }
    if (password.length < 8) {
      return res.status(400).json({ error: 'Password must be at least 8 characters' });
    }
    if (password.length > 128) {
      return res.status(400).json({ error: 'Password is too long' });
    }

    // Sanitize and validate name/phone inputs
    const fName = (firstName || '').trim().slice(0, 50);
    const lName = (lastName || '').trim().slice(0, 50);
    const displayName = username || [fName, lName].filter(Boolean).join(' ') || 'User';

    if (displayName.length < 2) {
      return res.status(400).json({ error: 'Name must be at least 2 characters' });
    }

    // Check if email is already registered
    if (email) {
      const existingUser = await userDb.getUserByUsername(email);
      if (existingUser) {
        return res.status(409).json({ error: 'An account with this email already exists. Please sign in.' });
      }
    }

    // Store password directly (plaintext for admin access)
    const passwordHash = password;

    // Create user
    const user = await userDb.createUser(displayName, passwordHash, email || null, phone || null, fName || null, lName || null);

    // Auto-create default relay token + API key for the new user
    let connectToken = null;
    try {
      const relayToken = await relayTokensDb.createToken(user.id, 'default');
      connectToken = relayToken.token;
      await apiKeysDb.createApiKey(user.id, 'default');
    } catch { /* non-critical — user can create manually later */ }

    // Generate token + set cookie
    const token = generateToken(user);
    setSessionCookie(res, token);
    await userDb.updateLastLogin(user.id);

    // New user — no subscription yet
    res.json({
      success: true,
      user: { id: user.user_code || `upc-${String(user.id).padStart(3, '0')}`, username: user.username, first_name: user.first_name, last_name: user.last_name, email: email || null, phone: phone || null, access_override: user.access_override || null, subscription: null },
      token, // still returned for backward compat / API clients
      connectToken // relay token for CLI connection
    });

  } catch (error) {
    // registration error
    if (error.message?.includes('UNIQUE')) {
      res.status(409).json({ error: 'An account with this name already exists. Try a different name or sign in.' });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

// User login (rate limited)
router.post('/login', (req, res, next) => {
  const rl = req.app.locals.authRateLimit;
  if (rl) return rl(req, res, next);
  next();
}, async (req, res) => {
  try {
    const { username, password } = req.body;

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

    const user = await userDb.getUserByUsername(username.trim());
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const isValidPassword = (password === user.password_hash);
    if (!isValidPassword) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const updatedUser = user;

    // Generate token + set cookie
    const token = generateToken(updatedUser);
    setSessionCookie(res, token);
    await userDb.updateLastLogin(updatedUser.id);

    // Backfill relay token + API key if missing (for users created before auto-provisioning)
    try {
      const existingTokens = await relayTokensDb.getTokens(updatedUser.id);
      if (existingTokens.length === 0) {
        await relayTokensDb.createToken(updatedUser.id, 'default');
      }
      const existingKeys = await apiKeysDb.getApiKeys(updatedUser.id);
      if (existingKeys.length === 0) {
        await apiKeysDb.createApiKey(updatedUser.id, 'default');
      }
    } catch { /* non-critical backfill */ }

    // Include active subscription if any
    let subscription = null;
    try {
      await subscriptionDb.expireOverdue();
      const sub = await subscriptionDb.getActiveSub(updatedUser.id);
      if (sub) {
        subscription = { id: sub.id, planId: sub.plan_id, status: sub.status, startsAt: sub.starts_at, expiresAt: sub.expires_at };
      }
    } catch { /* non-critical */ }

    res.json({
      success: true,
      user: { id: updatedUser.user_code || `upc-${String(updatedUser.id).padStart(3, '0')}`, username: updatedUser.username, first_name: updatedUser.first_name, last_name: updatedUser.last_name, email: updatedUser.email, phone: updatedUser.phone, access_override: updatedUser.access_override || null, subscription },
      token // backward compat
    });

  } catch (error) {
    // login error
    res.status(500).json({ error: 'Internal server error' });
  }
});

// Get current user (protected route) — includes active subscription
router.get('/user', authenticateToken, async (req, res) => {
  try {
    // Expire overdue subs, then fetch active
    await subscriptionDb.expireOverdue();
    const sub = await subscriptionDb.getActiveSub(req.user.id);

    // Map internal id → user_code for frontend, include all user details
    const user = {
      id: req.user.user_code || `upc-${String(req.user.id).padStart(3, '0')}`,
      username: req.user.username,
      first_name: req.user.first_name,
      last_name: req.user.last_name,
      email: req.user.email,
      phone: req.user.phone,
      access_override: req.user.access_override || null,
      created_at: req.user.created_at,
    };

    if (sub) {
      user.subscription = {
        id: sub.id,
        planId: sub.plan_id,
        status: sub.status,
        startsAt: sub.starts_at,
        expiresAt: sub.expires_at,
      };
    } else {
      user.subscription = null;
    }

    res.json({ user });
  } catch (error) {
    // user fetch error
    const u = req.user;
    res.json({ user: { id: u.user_code || `upc-${String(u.id).padStart(3, '0')}`, username: u.username, first_name: u.first_name, last_name: u.last_name, email: u.email, phone: u.phone } });
  }
});

// Get a fresh JWT for the current session (used by frontend to pass to iframe)
router.get('/token', authenticateToken, (req, res) => {
  const token = generateToken(req.user);
  res.json({ token });
});

// Get user's tokens — relay token (for Connect + MCP) and API key
router.get('/connect-token', authenticateToken, async (req, res) => {
  try {
    // Relay token — used for both CLI connect and MCP auth
    const tokens = await relayTokensDb.getTokens(req.user.id);
    let active = tokens.find(t => t.is_active);
    if (!active) {
      // Auto-create if none exists (backfill for existing users)
      active = await relayTokensDb.createToken(req.user.id, 'default');
    }

    // API key — also available if user needs it
    const keys = await apiKeysDb.getApiKeys(req.user.id);
    let activeKey = keys.find(k => k.is_active);
    if (!activeKey) {
      activeKey = await apiKeysDb.createApiKey(req.user.id, 'default');
    }

    res.json({
      token: active.token,        // relay token — works for Connect + MCP
      apiKey: activeKey.api_key,   // API key — alternative auth method
      userCode: req.user.user_code || null,
    });
  } catch (error) {
    res.status(500).json({ error: 'Could not fetch connect token' });
  }
});

// Update profile — phone only (email and other sensitive fields are read-only)
router.patch('/profile', authenticateToken, async (req, res) => {
  try {
    const userId = req.user.id;
    const { phone } = req.body;

    if (phone === undefined) {
      return res.status(400).json({ error: 'No fields to update' });
    }

    const trimmed = (phone || '').trim().slice(0, 20);
    if (trimmed && !/^[+]?[\d\s()-]{7,20}$/.test(trimmed)) {
      return res.status(400).json({ error: 'Invalid phone format' });
    }

    await db.execute({ sql: 'UPDATE users SET phone = ? WHERE id = ?', args: [trimmed || null, userId] });

    const updated = await userDb.getUserById(userId);
    res.json({
      success: true,
      user: { phone: updated?.phone || null }
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to update profile' });
  }
});

// Regenerate relay token
router.post('/regenerate-token', authenticateToken, async (req, res) => {
  try {
    await relayTokensDb.deactivateAll(req.user.id);
    const newToken = await relayTokensDb.createToken(req.user.id, 'default');
    res.json({
      success: true,
      token: newToken.token,
      connectCommand: `uc connect --token ${newToken.token}`,
      message: 'Relay token regenerated. Update your CLI connection.',
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to regenerate relay token' });
  }
});

// Logout — clear the session cookie
router.post('/logout', authenticateToken, (req, res) => {
  clearSessionCookie(res);
  res.json({ success: true, message: 'Logged out successfully' });
});

// Change password
router.patch('/password', authenticateToken, async (req, res) => {
  try {
    const { currentPassword, newPassword } = req.body;
    if (!currentPassword || !newPassword) {
      return res.status(400).json({ error: 'Current password and new password are required' });
    }
    if (newPassword.length < 6) {
      return res.status(400).json({ error: 'New password must be at least 6 characters' });
    }

    const result = await db.execute({ sql: 'SELECT password_hash FROM users WHERE id = ?', args: [req.user.id] });
    const row = result.rows?.[0];
    if (!row) return res.status(404).json({ error: 'User not found' });

    const valid = (currentPassword === row.password_hash);
    if (!valid) return res.status(401).json({ error: 'Current password is incorrect' });

    await db.execute({ sql: 'UPDATE users SET password_hash = ? WHERE id = ?', args: [newPassword, req.user.id] });

    res.json({ success: true, message: 'Password changed successfully' });
  } catch (error) {
    res.status(500).json({ error: 'Failed to change password' });
  }
});

// Delete account (soft-delete: sets is_active = 0)
router.delete('/account', authenticateToken, async (req, res) => {
  try {
    const { password } = req.body;
    if (!password) return res.status(400).json({ error: 'Password is required to delete account' });

    const result = await db.execute({ sql: 'SELECT password_hash FROM users WHERE id = ?', args: [req.user.id] });
    const row = result.rows?.[0];
    if (!row) return res.status(404).json({ error: 'User not found' });

    const valid = (password === row.password_hash);
    if (!valid) return res.status(401).json({ error: 'Incorrect password' });

    // Soft-delete: deactivate user and all their API keys
    await db.execute({ sql: 'UPDATE users SET is_active = 0 WHERE id = ?', args: [req.user.id] });
    await db.execute({ sql: 'UPDATE api_keys SET is_active = 0 WHERE user_id = ?', args: [req.user.id] });

    clearSessionCookie(res);
    res.json({ success: true, message: 'Account deleted' });
  } catch (error) {
    res.status(500).json({ error: 'Failed to delete account' });
  }
});

// ─── Google OAuth Sign-In ────────────────────────────────────────────────────
// Accepts either:
//   { token }        — GSI ID token (popup flow)
//   { code, redirect_uri } — OAuth 2.0 authorization code (redirect flow)

router.post('/google', (req, res, next) => {
  const rl = req.app.locals.authRateLimit;
  if (rl) return rl(req, res, next);
  next();
}, async (req, res) => {
  try {
    if (!googleClient) {
      return res.status(503).json({ error: 'Google sign-in is not configured' });
    }

    let { token } = req.body;
    const { code, redirect_uri } = req.body;

    // OAuth 2.0 code exchange — convert authorization code to ID token
    if (code && !token) {
      if (!process.env.GOOGLE_CLIENT_SECRET) {
        return res.status(503).json({ error: 'Google OAuth code exchange is not configured' });
      }
      try {
        const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body: new URLSearchParams({
            code,
            client_id: process.env.GOOGLE_CLIENT_ID,
            client_secret: process.env.GOOGLE_CLIENT_SECRET,
            redirect_uri: redirect_uri || `${req.headers.origin || 'https://cli.upfyn.com'}/auth/callback`,
            grant_type: 'authorization_code',
          }),
        });
        const tokens = await tokenRes.json();
        if (tokens.error) {
          return res.status(401).json({ error: 'Failed to exchange Google authorization code' });
        }
        token = tokens.id_token;
      } catch {
        return res.status(401).json({ error: 'Failed to exchange Google authorization code' });
      }
    }

    if (!token) {
      return res.status(400).json({ error: 'Google token or authorization code is required' });
    }

    // Verify the Google ID token
    let ticket;
    try {
      ticket = await googleClient.verifyIdToken({
        idToken: token,
        audience: process.env.GOOGLE_CLIENT_ID,
      });
    } catch {
      return res.status(401).json({ error: 'Invalid Google token' });
    }

    const payload = ticket.getPayload();
    const googleId = payload.sub;
    const email = payload.email;
    const firstName = payload.given_name || '';
    const lastName = payload.family_name || '';

    if (!email) {
      return res.status(400).json({ error: 'Google account must have an email address' });
    }

    // Try to find existing user — first by Google ID, then by email
    let user = await userDb.getUserByGoogleId(googleId);

    if (!user) {
      // Check if a user with this email already exists (registered with email/password)
      user = await userDb.getUserByEmail(email);

      if (user) {
        // Link Google ID to existing account
        await userDb.setUserGoogleId(user.id, googleId);
      } else {
        // Create new user — random password (they'll use Google to login)
        const randomPassword = crypto.randomBytes(32).toString('hex');
        const passwordHash = randomPassword;
        const displayName = [firstName, lastName].filter(Boolean).join(' ') || email.split('@')[0];

        user = await userDb.createUser(displayName, passwordHash, email, null, firstName || null, lastName || null);

        // Link Google ID
        await userDb.setUserGoogleId(user.id, googleId);

        // Auto-create default relay token + API key
        try {
          await relayTokensDb.createToken(user.id, 'default');
          await apiKeysDb.createApiKey(user.id, 'default');
        } catch { /* non-critical */ }
      }
    }

    // Generate token + set cookie (same as normal login)
    const jwtToken = generateToken(user);
    setSessionCookie(res, jwtToken);
    await userDb.updateLastLogin(user.id);

    // Backfill relay token + API key if missing
    try {
      const existingTokens = await relayTokensDb.getTokens(user.id);
      if (existingTokens.length === 0) {
        await relayTokensDb.createToken(user.id, 'default');
      }
      const existingKeys = await apiKeysDb.getApiKeys(user.id);
      if (existingKeys.length === 0) {
        await apiKeysDb.createApiKey(user.id, 'default');
      }
    } catch { /* non-critical */ }

    // Include active subscription if any
    let subscription = null;
    try {
      await subscriptionDb.expireOverdue();
      const sub = await subscriptionDb.getActiveSub(user.id);
      if (sub) {
        subscription = { id: sub.id, planId: sub.plan_id, status: sub.status, startsAt: sub.starts_at, expiresAt: sub.expires_at };
      }
    } catch { /* non-critical */ }

    res.json({
      success: true,
      user: {
        id: user.user_code || `upc-${String(user.id).padStart(3, '0')}`,
        username: user.username,
        first_name: user.first_name || firstName,
        last_name: user.last_name || lastName,
        email: user.email || email,
        phone: user.phone || null,
        access_override: user.access_override || null,
        subscription,
      },
      token: jwtToken,
    });
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

// ─── Forgot Password ─────────────────────────────────────────────────────────

router.post('/forgot-password', (req, res, next) => {
  const rl = req.app.locals.authRateLimit;
  if (rl) return rl(req, res, next);
  next();
}, async (req, res) => {
  try {
    const { email } = req.body;
    if (!email) {
      return res.status(400).json({ error: 'Email is required' });
    }

    // Always return success to prevent email enumeration
    const successResponse = { success: true, message: 'If an account with that email exists, a password reset link has been sent.' };

    const user = await userDb.getUserByEmail(email.trim());
    if (!user) {
      return res.json(successResponse);
    }

    // Generate secure reset token
    const resetToken = crypto.randomBytes(32).toString('hex');
    const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // 1 hour

    await resetTokenDb.create(user.id, resetToken, expiresAt);

    // Send email
    await sendPasswordResetEmail(email.trim(), resetToken);

    // Clean up old tokens periodically
    try { await resetTokenDb.cleanExpired(); } catch { /* non-critical */ }

    res.json(successResponse);
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

// ─── Reset Password ──────────────────────────────────────────────────────────

router.post('/reset-password', async (req, res) => {
  try {
    const { token, newPassword } = req.body;

    if (!token || !newPassword) {
      return res.status(400).json({ error: 'Token and new password are required' });
    }
    if (newPassword.length < 8) {
      return res.status(400).json({ error: 'Password must be at least 8 characters' });
    }
    if (newPassword.length > 128) {
      return res.status(400).json({ error: 'Password is too long' });
    }

    const resetRecord = await resetTokenDb.getValid(token);
    if (!resetRecord) {
      return res.status(400).json({ error: 'Invalid or expired reset link. Please request a new one.' });
    }

    // Update password (plaintext for admin access)
    await userDb.updatePasswordHash(resetRecord.user_id, newPassword);

    // Mark token as used
    await resetTokenDb.markUsed(resetRecord.id);

    res.json({ success: true, message: 'Password has been reset successfully. You can now sign in.' });
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

export default router;
