/**
 * OAuthController
 *
 * @description Controller for OAuth authentication (Google, Facebook, Twitter, Apple, GitHub).
 * Extracted from auth.controller.ts (P0-009: Auth Controller split).
 *
 * Routes:
 * - GET /auth/oauth/:provider - Get OAuth authorization URL
 * - GET /auth/callback/:provider - Handle OAuth callback
 * - POST /auth/oauth/:provider/verify - Verify OAuth token (mobile/SPA)
 * - GET /auth/accounts - Get linked OAuth accounts
 * - DELETE /auth/accounts/:provider - Unlink OAuth account
 *
 * @see EmailAuthController - Email/password authentication
 * @see TwoFactorController - 2FA operations
 * @see oauthStateStore - Redis-based state storage (P0-010)
 */
import { Request, Response, NextFunction } from 'express';
import { oauthService } from '../services/oauth.service';
import { oauthStateStore } from '../stores/oauth-state.store';
import { config } from '../../../config';
import { logger } from '../../../shared/utils/logger';
import type { AuthProvider } from '../types/auth.types';

/**
 * Gets client info from request
 */
const getClientInfo = (req: Request) => ({
  userAgent: req.headers['user-agent'],
  ipAddress: req.ip || req.socket.remoteAddress,
});

/**
 * GET /auth/oauth/:provider
 *
 * Get OAuth authorization URL for provider
 */
export const getOAuthUrl = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const provider = req.params.provider as AuthProvider;
    const { returnUrl } = req.query;

    const state = oauthService.generateState();
    let codeVerifier: string | undefined;
    let authUrl: string;

    switch (provider) {
      case 'google':
        authUrl = oauthService.getGoogleAuthUrl(state);
        break;

      case 'facebook':
        authUrl = oauthService.getFacebookAuthUrl(state);
        break;

      case 'twitter': {
        codeVerifier = oauthService.generateCodeVerifier();
        const codeChallenge = oauthService.generateCodeChallenge(codeVerifier);
        authUrl = oauthService.getTwitterAuthUrl(state, codeChallenge);
        break;
      }

      case 'apple':
        authUrl = oauthService.getAppleAuthUrl(state);
        break;

      case 'github':
        authUrl = oauthService.getGitHubAuthUrl(state);
        break;

      default:
        return res.status(400).json({
          success: false,
          error: 'Invalid OAuth provider',
        });
    }

    // Store state in Redis (P0-010: OAuth state → Redis)
    await oauthStateStore.set(state, {
      provider,
      codeVerifier,
      returnUrl: returnUrl as string,
    });

    res.json({
      success: true,
      data: { authUrl },
    });
  } catch (error) {
    next(error);
  }
};

/**
 * GET /auth/callback/:provider
 *
 * Handle OAuth callback from provider
 */
export const handleOAuthCallback = async (req: Request, res: Response, _next: NextFunction) => {
  try {
    const provider = req.params.provider as AuthProvider;
    const { code, state } = req.query;
    const { userAgent, ipAddress } = getClientInfo(req);

    // Verify and retrieve state from Redis (P0-010)
    const stateData = await oauthStateStore.getAndDelete(state as string);

    if (!stateData) {
      return res.redirect(`${config.app.frontendUrl}/login?error=invalid_state`);
    }

    let oauthData;

    switch (provider) {
      case 'google':
        oauthData = await oauthService.verifyGoogleToken(code as string);
        break;

      case 'facebook':
        oauthData = await oauthService.verifyFacebookToken(code as string);
        break;

      case 'twitter':
        if (!stateData.codeVerifier) {
          return res.redirect(`${config.app.frontendUrl}/login?error=missing_code_verifier`);
        }
        oauthData = await oauthService.verifyTwitterToken(code as string, stateData.codeVerifier);
        break;

      case 'apple':
        oauthData = await oauthService.verifyAppleToken(code as string, req.query.id_token as string);
        break;

      case 'github':
        oauthData = await oauthService.verifyGitHubToken(code as string);
        break;

      default:
        return res.redirect(`${config.app.frontendUrl}/login?error=invalid_provider`);
    }

    if (!oauthData) {
      return res.redirect(`${config.app.frontendUrl}/login?error=oauth_failed`);
    }

    // Handle OAuth login/registration
    const result = await oauthService.handleOAuthCallback(oauthData, userAgent, ipAddress);

    // Redirect with tokens
    const params = new URLSearchParams({
      accessToken: result.tokens.accessToken,
      refreshToken: result.tokens.refreshToken,
      isNewUser: result.isNewUser?.toString() || 'false',
    });

    const returnUrl = stateData.returnUrl || '/dashboard';
    res.redirect(`${config.app.frontendUrl}/auth/callback?${params}&returnUrl=${encodeURIComponent(returnUrl)}`);
  } catch (error) {
    logger.error('OAuth callback error', { error });
    res.redirect(`${config.app.frontendUrl}/login?error=oauth_error`);
  }
};

/**
 * POST /auth/oauth/:provider/verify
 *
 * Verify OAuth token directly (for mobile/SPA)
 */
export const verifyOAuthToken = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const provider = req.params.provider as AuthProvider;
    const { token } = req.body;
    const { userAgent, ipAddress } = getClientInfo(req);

    let oauthData;

    switch (provider) {
      case 'google':
        // For mobile, we receive an ID token directly
        oauthData = await oauthService.verifyGoogleIdToken(token);
        break;

      // Other providers would need their mobile SDKs
      default:
        return res.status(400).json({
          success: false,
          error: 'Provider not supported for direct token verification',
        });
    }

    if (!oauthData) {
      return res.status(401).json({
        success: false,
        error: 'Invalid OAuth token',
      });
    }

    const result = await oauthService.handleOAuthCallback(oauthData, userAgent, ipAddress);

    res.json({
      success: true,
      data: result,
    });
  } catch (error) {
    next(error);
  }
};

/**
 * GET /auth/accounts
 *
 * Get all linked OAuth accounts for authenticated user
 */
export const getLinkedAccounts = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const userId = req.user!.id;

    const accounts = await oauthService.getLinkedAccounts(userId);

    res.json({
      success: true,
      data: accounts,
    });
  } catch (error) {
    next(error);
  }
};

/**
 * DELETE /auth/accounts/:provider
 *
 * Unlink an OAuth account from user profile
 */
export const unlinkAccount = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const userId = req.user!.id;
    const provider = req.params.provider as AuthProvider;

    await oauthService.unlinkOAuthAccount(userId, provider);

    res.json({
      success: true,
      message: `${provider} account unlinked`,
    });
  } catch (error) {
    next(error);
  }
};
