'use strict';

const router    = require('express').Router();
const bcrypt    = require('bcryptjs');
const crypto    = require('crypto');
const { v4: uuidv4 } = require('uuid');
const { body, validationResult } = require('express-validator');
const { google } = require('googleapis');
const { query, queryOne, transaction } = require('../config/db');
const { signJwt, signRefreshToken, hashApiKey, generateApiKey } = require('../middleware/auth');
const { sendEmail } = require('../services/email');
const { logAudit } = require('../services/audit');
const rateLimit = require('express-rate-limit');

// Strict rate limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max:      parseInt(process.env.RATE_LIMIT_AUTH_MAX || '10'),
  message:  { error: 'Too many attempts. Try again in 15 minutes.' },
  standardHeaders: true,
  legacyHeaders:   false,
});

// ── Google OAuth client ───────────────────────────────────────────
const oauthClient = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  process.env.GOOGLE_REDIRECT_URI
);

// ── POST /auth/register ───────────────────────────────────────────
router.post('/register',
  authLimiter,
  [
    body('name').trim().isLength({ min: 2, max: 120 }).withMessage('Name must be 2–120 chars.'),
    body('email').isEmail().normalizeEmail().withMessage('Valid email required.'),
    body('password').isLength({ min: 8 }).withMessage('Password min 8 characters.'),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(422).json({ errors: errors.array() });
    }

    const { name, email, password, store_name, store_url, platform } = req.body;

    try {
      // Check existing user
      const existing = await queryOne('SELECT id FROM users WHERE email = ?', [email]);
      if (existing) {
        return res.status(409).json({ error: 'An account with this email already exists.' });
      }

      const passwordHash   = await bcrypt.hash(password, 12);
      const uuid           = uuidv4();
      const verifyToken    = crypto.randomBytes(32).toString('hex');
      const avatar         = name.trim()[0].toUpperCase();

      await transaction(async (conn) => {
        // Create user
        const [userResult] = await conn.execute(
          `INSERT INTO users (uuid, name, email, password_hash, avatar, email_verify_token)
           VALUES (?, ?, ?, ?, ?, ?)`,
          [uuid, name.trim(), email, passwordHash, avatar, verifyToken]
        );
        const userId = userResult.insertId;

        // Default settings
        await conn.execute(
          'INSERT INTO user_settings (user_id) VALUES (?)',
          [userId]
        );

        // Create store if provided
        if (store_name) {
          await conn.execute(
            `INSERT INTO stores (uuid, user_id, name, url, platform)
             VALUES (?, ?, ?, ?, ?)`,
            [uuidv4(), userId, store_name, store_url || '', platform || 'woocommerce']
          );
        }

        // Generate first API key
        const { raw, hash, preview, prefix } = generateApiKey('live');
        await conn.execute(
          `INSERT INTO api_keys (uuid, user_id, name, key_hash, key_prefix, key_preview, scope, environment)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
          [uuidv4(), userId, 'Default Key', hash, prefix, preview, 'full', 'live']
        );

        // Send verification email (non-blocking)
        sendEmail({
          to:      email,
          subject: 'Verify your ChainPay account',
          template:'verify_email',
          data:    { name, token: verifyToken },
        }).catch(console.error);

        await logAudit(conn, userId, 'user.register', 'user', String(userId), { email });
      });

      const user = await queryOne(
        'SELECT id, uuid, name, email, role, plan, avatar FROM users WHERE email = ?',
        [email]
      );

      const accessToken  = signJwt(user.id, { role: user.role, plan: user.plan });
      const refreshToken = signRefreshToken(user.id);

      // Store refresh token hash
      await query(
        `INSERT INTO refresh_tokens (user_id, token_hash, ip, user_agent, expires_at)
         VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))`,
        [user.id, crypto.createHash('sha256').update(refreshToken).digest('hex'), req.ip, req.headers['user-agent'] || '']
      );

      return res.status(201).json({
        user:          { uuid: user.uuid, name: user.name, email: user.email, role: user.role, plan: user.plan, avatar: user.avatar },
        access_token:  accessToken,
        refresh_token: refreshToken,
        token_type:    'Bearer',
        expires_in:    604800, // 7 days
      });

    } catch (err) {
      console.error('[AUTH] Register error:', err);
      return res.status(500).json({ error: 'Registration failed. Please try again.' });
    }
  }
);

// ── POST /auth/login ──────────────────────────────────────────────
router.post('/login',
  authLimiter,
  [
    body('email').isEmail().normalizeEmail(),
    body('password').notEmpty(),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(422).json({ errors: errors.array() });
    }

    const { email, password } = req.body;

    try {
      const user = await queryOne(
        'SELECT * FROM users WHERE email = ?',
        [email]
      );

      if (!user || !user.password_hash) {
        return res.status(401).json({ error: 'Invalid email or password.' });
      }

      if (user.status === 'suspended') {
        return res.status(403).json({ error: 'Account suspended. Contact support@chainpay.store.' });
      }

      const valid = await bcrypt.compare(password, user.password_hash);
      if (!valid) {
        await logAudit(null, user.id, 'user.login_failed', 'user', String(user.id), { ip: req.ip });
        return res.status(401).json({ error: 'Invalid email or password.' });
      }

      // Update last login
      await query(
        'UPDATE users SET last_login_at = NOW(), last_login_ip = ? WHERE id = ?',
        [req.ip, user.id]
      );

      const accessToken  = signJwt(user.id, { role: user.role, plan: user.plan });
      const refreshToken = signRefreshToken(user.id);

      await query(
        `INSERT INTO refresh_tokens (user_id, token_hash, ip, user_agent, expires_at)
         VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))`,
        [user.id, crypto.createHash('sha256').update(refreshToken).digest('hex'), req.ip, req.headers['user-agent'] || '']
      );

      await logAudit(null, user.id, 'user.login', 'user', String(user.id), { ip: req.ip });

      return res.json({
        user: {
          uuid:   user.uuid,
          name:   user.name,
          email:  user.email,
          role:   user.role,
          plan:   user.plan,
          avatar: user.avatar,
        },
        access_token:  accessToken,
        refresh_token: refreshToken,
        token_type:    'Bearer',
        expires_in:    604800,
      });

    } catch (err) {
      console.error('[AUTH] Login error:', err);
      return res.status(500).json({ error: 'Login failed. Please try again.' });
    }
  }
);

// ── GET /auth/google ──────────────────────────────────────────────
router.get('/google', (req, res) => {
  const url = oauthClient.generateAuthUrl({
    access_type: 'offline',
    scope: ['profile', 'email'],
    state: req.query.redirect || '/',
  });
  res.redirect(url);
});

// ── GET /auth/google/callback ─────────────────────────────────────
router.get('/google/callback', async (req, res) => {
  const { code, error } = req.query;

  if (error || !code) {
    return res.redirect(`${process.env.PLATFORM_DOMAIN ? 'https://'+process.env.PLATFORM_DOMAIN : 'http://localhost:3000'}/login?error=google_auth_failed`);
  }

  try {
    const { tokens }  = await oauthClient.getToken(code);
    oauthClient.setCredentials(tokens);

    const oauth2      = google.oauth2({ version: 'v2', auth: oauthClient });
    const { data }    = await oauth2.userinfo.get();

    const { id: googleId, email, name, picture } = data;

    // Find or create user
    let user = await queryOne(
      'SELECT * FROM users WHERE google_id = ? OR email = ?',
      [googleId, email]
    );

    if (!user) {
      // New user via Google
      const uuid   = uuidv4();
      const avatar = (name || email || 'G')[0].toUpperCase();

      await transaction(async (conn) => {
        const [r] = await conn.execute(
          `INSERT INTO users (uuid, name, email, avatar, google_id, google_email, email_verified, role)
           VALUES (?, ?, ?, ?, ?, ?, 1, 'client')`,
          [uuid, name || email, email, avatar, googleId, email]
        );
        const userId = r.insertId;
        await conn.execute('INSERT INTO user_settings (user_id) VALUES (?)', [userId]);
        const { hash, preview, prefix } = generateApiKey('live');
        await conn.execute(
          `INSERT INTO api_keys (uuid, user_id, name, key_hash, key_prefix, key_preview, scope, environment)
           VALUES (?, ?, 'Default Key', ?, ?, ?, 'full', 'live')`,
          [uuidv4(), userId, hash, prefix, preview]
        );
      });

      user = await queryOne('SELECT * FROM users WHERE email = ?', [email]);
    } else if (!user.google_id) {
      // Link Google to existing account
      await query('UPDATE users SET google_id = ?, google_email = ?, email_verified = 1 WHERE id = ?', [googleId, email, user.id]);
    }

    const accessToken  = signJwt(user.id, { role: user.role, plan: user.plan });
    const refreshToken = signRefreshToken(user.id);

    await query(
      `INSERT INTO refresh_tokens (user_id, token_hash, ip, expires_at)
       VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))`,
      [user.id, crypto.createHash('sha256').update(refreshToken).digest('hex'), req.ip]
    );

    await query('UPDATE users SET last_login_at = NOW(), last_login_ip = ? WHERE id = ?', [req.ip, user.id]);

    // Redirect to dashboard with tokens in URL fragment (SPA) or set cookie
    const baseUrl = process.env.PLATFORM_DOMAIN ? `https://${process.env.PLATFORM_DOMAIN}` : 'http://localhost:3001';
    const redirect = user.role === 'admin' || user.role === 'superadmin' ? '/admin.html' : '/dashboard.html';

    return res.redirect(
      `${baseUrl}${redirect}?access_token=${accessToken}&refresh_token=${refreshToken}`
    );

  } catch (err) {
    console.error('[AUTH] Google callback error:', err);
    return res.redirect(`${process.env.PLATFORM_DOMAIN ? 'https://'+process.env.PLATFORM_DOMAIN : 'http://localhost:3001'}/login?error=google_auth_error`);
  }
});

// ── POST /auth/refresh ────────────────────────────────────────────
router.post('/refresh', async (req, res) => {
  const { refresh_token } = req.body;
  if (!refresh_token) {
    return res.status(400).json({ error: 'refresh_token required.' });
  }

  try {
    const payload    = require('jsonwebtoken').verify(
      refresh_token,
      process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET
    );
    const tokenHash  = crypto.createHash('sha256').update(refresh_token).digest('hex');
    const stored     = await queryOne(
      'SELECT * FROM refresh_tokens WHERE token_hash = ? AND user_id = ? AND expires_at > NOW()',
      [tokenHash, payload.sub]
    );

    if (!stored) {
      return res.status(401).json({ error: 'Refresh token invalid or expired.' });
    }

    const user = await queryOne(
      'SELECT id, uuid, name, email, role, plan, avatar FROM users WHERE id = ? AND status = "active"',
      [payload.sub]
    );

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

    const newAccess  = signJwt(user.id, { role: user.role, plan: user.plan });
    const newRefresh = signRefreshToken(user.id);
    const newHash    = crypto.createHash('sha256').update(newRefresh).digest('hex');

    // Rotate refresh token
    await query('DELETE FROM refresh_tokens WHERE id = ?', [stored.id]);
    await query(
      `INSERT INTO refresh_tokens (user_id, token_hash, ip, expires_at)
       VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))`,
      [user.id, newHash, req.ip]
    );

    return res.json({ access_token: newAccess, refresh_token: newRefresh, token_type: 'Bearer', expires_in: 604800 });

  } catch (err) {
    return res.status(401).json({ error: 'Invalid refresh token.' });
  }
});

// ── POST /auth/logout ─────────────────────────────────────────────
router.post('/logout', async (req, res) => {
  const { refresh_token } = req.body;
  if (refresh_token) {
    const hash = crypto.createHash('sha256').update(refresh_token).digest('hex');
    await query('DELETE FROM refresh_tokens WHERE token_hash = ?', [hash]).catch(() => {});
  }
  return res.json({ ok: true });
});

// ── POST /auth/forgot-password ────────────────────────────────────
router.post('/forgot-password', authLimiter, async (req, res) => {
  const { email } = req.body;
  if (!email) return res.status(400).json({ error: 'Email required.' });

  const user = await queryOne('SELECT id, name FROM users WHERE email = ?', [email]);

  // Always return 200 to prevent email enumeration
  if (user) {
    const token   = crypto.randomBytes(32).toString('hex');
    const expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
    await query('UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?', [token, expires, user.id]);
    await sendEmail({
      to: email,
      subject: 'Reset your ChainPay password',
      template: 'reset_password',
      data: { name: user.name, token },
    }).catch(console.error);
  }

  return res.json({ ok: true, message: 'If an account exists, a reset email has been sent.' });
});

// ── POST /auth/reset-password ─────────────────────────────────────
router.post('/reset-password', authLimiter, async (req, res) => {
  const { token, password } = req.body;
  if (!token || !password || password.length < 8) {
    return res.status(400).json({ error: 'Valid token and password (min 8 chars) required.' });
  }

  const user = await queryOne(
    'SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()',
    [token]
  );

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

  const hash = await bcrypt.hash(password, 12);
  await query('UPDATE users SET password_hash = ?, reset_token = NULL, reset_expires = NULL WHERE id = ?', [hash, user.id]);

  return res.json({ ok: true, message: 'Password updated. You can now sign in.' });
});

// ── GET /auth/me ──────────────────────────────────────────────────
router.get('/me', require('../middleware/auth').requireAuth, async (req, res) => {
  const user = await queryOne(
    `SELECT u.uuid, u.name, u.email, u.role, u.plan, u.avatar, u.email_verified, u.created_at,
            us.invoice_timeout, us.smart_routing, us.show_qr, us.show_timer, us.enabled_coins
     FROM users u
     LEFT JOIN user_settings us ON us.user_id = u.id
     WHERE u.id = ?`,
    [req.user.id]
  );
  if (!user) return res.status(404).json({ error: 'User not found.' });
  return res.json({ user });
});

module.exports = router;
