const LoginService = require('../../services/login/LoginService');
const MailService = require('../../services/email/MailService');
const { checkAndIncrementRateLimit } = require('../../middleware/rateLimiter');
const { logger } = require('../../middleware/logger');
const UnitOfWork = require('../../database/UnitOfWork');
const db = require('../../database/database');

// ---- Refresh token cookie helpers (keep aligned with BFF/frontend expectations) ----
function normalizeSameSite(v) {
  const raw = String(v || '').toLowerCase();
  if (raw === 'none') return 'none';
  if (raw === 'strict') return 'strict';
  return 'lax'; // default
}

function getRefreshCookieDomain() {
  // In dev/localhost, setting Domain breaks cookies; only set Domain in prod (or if explicitly provided).
  const envDomain = process.env.COOKIE_DOMAIN || process.env.REFRESH_COOKIE_DOMAIN;
  if (envDomain) return envDomain;
  if (process.env.NODE_ENV === 'production') return '.profit-planet.partners';
  return undefined;
}

function shouldUseSecureCookie(req) {
  // Works behind proxies if x-forwarded-proto is present; still prefer explicit env override.
  const override = process.env.COOKIE_SECURE;
  if (override != null) return String(override).toLowerCase() === 'true';
  const xfProto = String(req.headers['x-forwarded-proto'] || '').toLowerCase();
  const isHttps = req.secure || xfProto === 'https';
  return process.env.NODE_ENV === 'production' || isHttps;
}

function buildRefreshCookieOptions(req, refreshTokenExpires) {
  const sameSite = normalizeSameSite(process.env.COOKIE_SAMESITE || 'lax');
  const secure = shouldUseSecureCookie(req);

  // SameSite=None requires Secure; force it if configured that way.
  const finalSecure = sameSite === 'none' ? true : secure;

  const expires = refreshTokenExpires ? new Date(refreshTokenExpires) : undefined;
  const maxAge = expires ? Math.max(0, expires.getTime() - Date.now()) : undefined;

  return {
    domain: getRefreshCookieDomain(), // e.g. .profit-planet.partners (shared across subdomains)
    path: '/',                        // important for consistent clearing
    httpOnly: true,
    secure: finalSecure,
    sameSite,
    ...(expires ? { expires } : {}),
    ...(typeof maxAge === 'number' ? { maxAge } : {})
  };
}

function buildRefreshCookieClearOptions(req) {
  // Must match Domain+Path+Secure+SameSite used when setting.
  const opts = buildRefreshCookieOptions(req, null);
  return {
    ...opts,
    expires: new Date(0),
    maxAge: 0
  };
}

class LoginController {
  static async login(req, res) {
    try {
      const { email, password, lang } = req.body;
      logger.info('[LOGIN][REQ] Received lang from frontend', { lang });

      // Step 1: Check rate limit BEFORE processing credentials
      logger.info('[LOGIN][RATE LIMIT] Checking rate limit for IP', { ip: req.ip });
      const rateLimitExceeded = await checkAndIncrementRateLimit({
        rateKey: `login:${req.ip}`,
        max: 10,
        windowSeconds: 600
      }, null); // null for res, just check, don't send response

      if (rateLimitExceeded) {
        logger.warn('[RATE LIMIT] Blocked login attempt', { ip: req.ip, time: new Date() });
        return res.status(429).json({
          success: false,
          message: 'Rate limit exceeded. Please try again later.'
        });
      }

      let result;
      const uow = new UnitOfWork(); // No need to pass pool
      try {
        // Step 3: If not blocked, check credentials
        logger.info('[LOGIN][AUTH] Checking credentials', { email });
        result = await LoginService.login(email, password);
        logger.info('[LOGIN][AUTH] Credentials valid', { email });
      } catch (error) {
        // Step 4: If credentials invalid, increment rate limit
        logger.warn('[LOGIN][AUTH] Invalid credentials', { email, ip: req.ip });
        await checkAndIncrementRateLimit({
          rateKey: `login:${req.ip}`,
          max: 10,
          windowSeconds: 600
        }, res); // increment and send response if needed
        // If rate limit exceeded, response is already sent
        if (res.headersSent) {
          logger.warn('[RATE LIMIT] Rollback after failed login attempt', { ip: req.ip, time: new Date() });
          return;
        }
        if (error.status) {
          logger.warn('[AUTH] Rollback due to invalid credentials', { email, ip: req.ip, time: new Date() });
          return res.status(error.status).json({ success: false, message: error.message });
        }
        logger.error('Login error', { error });
        return res.status(500).json({ success: false, message: 'Internal server error' });
      }

      // Step 5: If credentials valid, allow login (do NOT increment rate limit)
      // Set refresh token as HTTP-only cookie (shared across .profit-planet.partners)
      res.cookie('refreshToken', result.refreshToken, buildRefreshCookieOptions(req, result.refreshTokenExpires));

      // --- Send login notification email ---
      try {
        await MailService.sendLoginNotificationEmail({
          email: result.user.email,
          ip: req.ip,
          loginTime: new Date(),
          userAgent: req.headers['user-agent'] || '',
          lang: lang // <-- pass lang from request body
        });
      } catch (mailError) {
        logger.error('Error sending login notification email', {
          message: mailError?.message,
          stack: mailError?.stack,
        });
        // Do not block login
      }

      logger.info('[LOGIN][SUCCESS] Login successful', { email, ip: req.ip });
      return res.status(200).json({
        success: true,
        accessToken: result.accessToken,
        user: result.user
      });
    } catch (error) {
      // Should not reach here, but fallback
      logger.error('Login error (outer catch)', { error });
      if (error && error.stack) {
        logger.error('Error stack', { stack: error.stack });
      }
      return res.status(500).json({ success: false, message: 'Internal server error' });
    }
  }

  static async refresh(req, res) {
    try {
      const refreshToken = req.cookies.refreshToken;
      if (!refreshToken) {
        logger.warn('No refresh token provided');
        return res.status(401).json({ success: false, message: 'No refresh token provided' });
      }

      const result = await LoginService.refresh(refreshToken);

      // Always set the rotated refresh token cookie with consistent attributes.
      res.cookie('refreshToken', result.refreshToken, buildRefreshCookieOptions(req, result.refreshTokenExpires));

      logger.info('Refresh token rotated', { userId: result.user.id });
      return res.status(200).json({
        success: true,
        accessToken: result.accessToken,
        user: result.user
      });
    } catch (error) {
      if (error.status) {
        logger.warn('Refresh error', { error });
        return res.status(error.status).json({ success: false, message: error.message });
      }
      logger.error('Refresh error', { error });
      return res.status(500).json({ success: false, message: 'Internal server error' });
    }
  }

  static async logout(req, res) {
    try {
      // Very detailed request-level logs
      logger.info('[LOGOUT][REQ] Logout request received', {
        ip: req.ip,
        method: req.method,
        url: req.originalUrl,
        userAgent: req.headers['user-agent'],
        cookieKeys: Object.keys(req.cookies || {})
      });

      // helper to mask tokens in logs (avoid leaking full token)
      const mask = (s) => {
        if (!s || typeof s !== 'string') return null;
        if (s.length <= 12) return `${s.slice(0, 3)}...${s.slice(-3)}`;
        return `${s.slice(0, 6)}...${s.slice(-6)}`;
      };

      const refreshToken = req.cookies.refreshToken;
      logger.debug('[LOGOUT] refreshToken presence', { present: !!refreshToken, tokenMasked: mask(refreshToken) });

      if (!refreshToken) {
        logger.warn('[LOGOUT] No refresh token provided for logout; still attempting to clear cookie on backend');

        // Try to set expired cookie header explicitly (backend-only for httpOnly)
        try {
          res.cookie('refreshToken', '', {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'strict',
            path: '/',
            expires: new Date(0)
          });
          const setCookieHeader = res.getHeader && res.getHeader('Set-Cookie');
          logger.info('[LOGOUT] Set-Cookie header after expiring (no token)', { setCookieHeader });
        } catch (cookieErr) {
          logger.error('[LOGOUT] Error while setting expired cookie (no token)', { error: cookieErr });
        }

        try {
          res.clearCookie('refreshToken', {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'strict',
            path: '/'
          });
          logger.info('[LOGOUT] res.clearCookie called (no token)');
        } catch (clearErr) {
          logger.error('[LOGOUT] Error while calling res.clearCookie (no token)', { error: clearErr });
        }

        logger.info('[LOGOUT] Response headersSent (no token)', { headersSent: !!res.headersSent });
        return res.status(400).json({ success: false, message: 'No refresh token provided' });
      }

      logger.info('[LOGOUT] Calling LoginService.logout', { tokenMasked: mask(refreshToken), ip: req.ip });
      try {
        await LoginService.logout(refreshToken);
        logger.info('[LOGOUT] LoginService.logout completed', { ip: req.ip });
      } catch (serviceErr) {
        logger.error('[LOGOUT] LoginService.logout failed', { error: serviceErr });
        // continue to attempt to clear cookie even if service logout fails
      }

      // Clear cookie using SAME Domain+Path+SameSite+Secure
      try {
        res.cookie('refreshToken', '', buildRefreshCookieClearOptions(req));
      } catch (cookieErr) {
        logger.error('[LOGOUT] Error while setting expired cookie', { error: cookieErr });
      }

      try {
        res.clearCookie('refreshToken', buildRefreshCookieClearOptions(req));
      } catch (clearErr) {
        logger.error('[LOGOUT] Error while calling res.clearCookie', { error: clearErr });
      }

      logger.info('[LOGOUT][SUCCESS] Logged out successfully (response about to be sent)', { ip: req.ip });
      return res.status(200).json({ success: true, message: 'Logged out successfully' });
    } catch (error) {
      logger.error('[LOGOUT][ERROR] Unexpected logout error', { error, stack: error && error.stack });
      return res.status(500).json({ success: false, message: 'Internal server error' });
    }
  }

  static async validate(req, res) {
    try {
      const refreshToken = req.cookies?.refreshToken;
      if (!refreshToken) {
        return res.status(401).json({ ok: false, message: 'No refresh token provided' });
      }

      const result = await LoginService.validateByRefreshToken(refreshToken);
      const user = result?.user || {};

      const role = String(user.role || '').toLowerCase();
      const userRoles = Array.isArray(user.roles)
        ? user.roles.map((r) => String(r).toLowerCase())
        : [];
      const mergedRoles = [...new Set([role, ...userRoles].filter(Boolean))];

      const isAdmin = Boolean(
        user.isAdmin ||
        role === 'admin' ||
        role === 'super_admin' ||
        role === 'superadmin' ||
        mergedRoles.includes('admin') ||
        mergedRoles.includes('super_admin') ||
        mergedRoles.includes('superadmin')
      );

      return res.status(200).json({
        ok: true,
        user: {
          id: String(user.id ?? ''),
          role: user.role || null,
          isAdmin,
          roles: mergedRoles,
        },
        isAdmin,
        role: user.role || null,
        roles: mergedRoles,
      });
    } catch (error) {
      if (error.status) {
        return res.status(error.status).json({ ok: false, message: error.message });
      }
      logger.error('authValidate:error', {
        message: error?.message,
        stack: error?.stack,
      });
      return res.status(500).json({ ok: false, message: 'Internal server error' });
    }
  }
}

module.exports = LoginController;