import { Request, Response } from 'express';
import { AuthService } from '../services/auth.service';
import { validateSSOCallback, validateRefreshToken, validateTokenExchange, validatePasswordLogin } from '../validators/auth.validator';
import { ResponseHandler } from '../utils/responseHandler';
import type { AuthenticatedRequest } from '../types/express';
import logger from '../utils/logger';
import { activityService, SYSTEM_EVENT_REQUEST_ID } from '../services/activity.service';
import { getRequestMetadata } from '../utils/requestUtils';
import { ACCESS_TOKEN_TTL_MS, REFRESH_TOKEN_TTL_MS } from '../config/sessionPolicy';
import crypto from 'crypto';

export class AuthController {
  private authService: AuthService;
  // One-time code usage guard (in-memory, per instance).
  private readonly consumedAuthCodes = new Map<string, number>();
  private readonly authCodeTtlMs = 10 * 60 * 1000;

  constructor() {
    this.authService = new AuthService();
  }

  private getCodeDigest(code: string): string {
    return crypto.createHash('sha256').update(code).digest('hex');
  }

  private pruneConsumedCodes(now: number): void {
    for (const [digest, ts] of this.consumedAuthCodes.entries()) {
      if (now - ts > this.authCodeTtlMs) this.consumedAuthCodes.delete(digest);
    }
  }

  private hasConsumedCode(code: string): boolean {
    const now = Date.now();
    this.pruneConsumedCodes(now);
    return this.consumedAuthCodes.has(this.getCodeDigest(code));
  }

  private markCodeConsumed(code: string): void {
    const now = Date.now();
    this.pruneConsumedCodes(now);
    this.consumedAuthCodes.set(this.getCodeDigest(code), now);
  }

  /**
   * Handle SSO callback from frontend
   * POST /api/v1/auth/sso-callback
   */
  async handleSSOCallback(req: Request, res: Response): Promise<void> {
    try {
      // Validate request body
      const validatedData = validateSSOCallback(req.body);

      const userAgent = req.headers['user-agent'] || getRequestMetadata(req).userAgent;
      const result = await this.authService.handleSSOCallback(validatedData as any, userAgent);

      // Log login activity
      const requestMeta = getRequestMetadata(req);
      await activityService.log({
        requestId: SYSTEM_EVENT_REQUEST_ID, // Special UUID for system events
        type: 'login',
        user: {
          userId: result.user.userId,
          name: result.user.displayName || result.user.email,
          email: result.user.email
        },
        timestamp: new Date().toISOString(),
        action: 'User Login',
        details: `User logged in via SSO from ${requestMeta.ipAddress || 'unknown IP'}`,
        metadata: {
          loginMethod: 'SSO',
          employeeId: result.user.employeeId,
          department: result.user.department,
          role: result.user.role
        },
        ipAddress: requestMeta.ipAddress,
        userAgent: requestMeta.userAgent,
        category: 'AUTHENTICATION',
        severity: 'INFO'
      });

      ResponseHandler.success(res, {
        user: result.user,
        accessToken: result.accessToken,
        refreshToken: result.refreshToken
      }, 'Authentication successful');
    } catch (error) {
      logger.error('SSO callback failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      ResponseHandler.error(res, 'Authentication failed', 400, errorMessage);
    }
  }

  /**
   * Get current user profile
   * GET /api/v1/auth/me
   */
  async getCurrentUser(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      const user = await this.authService.getUserProfile(req.user.userId);

      if (!user) {
        ResponseHandler.notFound(res, 'User not found');
        return;
      }

      ResponseHandler.success(res, {
        userId: user.userId,
        employeeId: user.employeeId,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        displayName: user.displayName,
        department: user.department,
        designation: user.designation,
        jobTitle: user.jobTitle,
        phone: user.phone,
        location: user.location,
        role: user.role,
        isActive: user.isActive,
        lastLogin: user.lastLogin,
        createdAt: user.createdAt,
        updatedAt: user.updatedAt
      }, 'User profile retrieved successfully');
    } catch (error) {
      logger.error('Failed to get current user:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      ResponseHandler.error(res, 'Failed to get user profile', 500, errorMessage);
    }
  }

  /**
   * Refresh access token
   * POST /api/v1/auth/refresh
   * Supports both request body and cookie-based refresh tokens
   */
  async refreshToken(req: Request, res: Response): Promise<void> {
    try {
      // Try to get refresh token from request body first, then from cookies
      let refreshToken: string | undefined;

      if (req.body?.refreshToken) {
        const validated = validateRefreshToken(req.body);
        refreshToken = validated.refreshToken;
      } else if ((req as any).cookies?.refreshToken) {
        // Fallback to cookie if available (requires cookie-parser middleware)
        refreshToken = (req as any).cookies.refreshToken;
      }

      if (!refreshToken) {
        res.status(400).json({
          success: false,
          error: 'Refresh token is required in request body or cookies',
          message: 'Request body validation failed',
          timestamp: new Date().toISOString()
        });
        return;
      }

      const refreshResult = await this.authService.refreshAccessToken(refreshToken);

      // Set new access token in cookie if using cookie-based auth
      const isProduction = process.env.NODE_ENV === 'production';
      const isUat = process.env.NODE_ENV === 'uat';
      const isSecureEnv = isProduction || isUat;

      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: isSecureEnv ? 'lax' as const : 'lax' as const, // 'lax' is safer and works on same-domain
        maxAge: Math.max(1000, refreshResult.accessTokenTtlMs),
      };

      res.cookie('accessToken', refreshResult.accessToken, cookieOptions);

      // SECURITY: In production, don't return token in response body
      // Token is securely stored in httpOnly cookie
      if (isProduction) {
        ResponseHandler.success(res, {
          message: 'Token refreshed successfully'
        }, 'Token refreshed successfully');
      } else {
        // Dev: Include token for debugging
        ResponseHandler.success(res, {
          accessToken: refreshResult.accessToken
        }, 'Token refreshed successfully');
      }
    } catch (error) {
      logger.error('Token refresh failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      ResponseHandler.error(res, 'Token refresh failed', 401, errorMessage);
    }
  }

  /**
   * Exchange Tanflow authorization code for tokens
   * POST /api/v1/auth/tanflow/token-exchange
   */
  async exchangeTanflowToken(req: Request, res: Response): Promise<void> {
    try {
      logger.info('Tanflow token exchange request received', {
        body: {
          code: req.body?.code ? `${req.body.code.substring(0, 10)}...` : 'MISSING',
          redirectUri: req.body?.redirectUri,
          state: req.body?.state ? 'PRESENT' : 'MISSING',
        },
      });

      const { code, redirectUri } = validateTokenExchange(req.body);
      logger.info('Tanflow token exchange validation passed', { redirectUri });

      const userAgent = req.headers['user-agent'] || getRequestMetadata(req).userAgent;
      const result = await this.authService.exchangeTanflowCodeForTokens(code, redirectUri, userAgent);

      // Log login activity
      const requestMeta = getRequestMetadata(req);
      await activityService.log({
        requestId: SYSTEM_EVENT_REQUEST_ID,
        type: 'login',
        user: {
          userId: result.user.userId,
          name: result.user.displayName || result.user.email,
          email: result.user.email
        },
        timestamp: new Date().toISOString(),
        action: 'User Login',
        details: `User logged in via Tanflow SSO from ${requestMeta.ipAddress || 'unknown IP'}`,
        metadata: {
          loginMethod: 'TANFLOW_SSO',
          employeeId: result.user.employeeId,
          department: result.user.department,
          role: result.user.role
        },
        ipAddress: requestMeta.ipAddress,
        userAgent: requestMeta.userAgent,
        category: 'AUTHENTICATION',
        severity: 'INFO'
      });

      // Set tokens in httpOnly cookies (production) or return in body (development)
      const isProduction = process.env.NODE_ENV === 'production';
      const isUat = process.env.NODE_ENV === 'uat';
      const isSecureEnv = isProduction || isUat;

      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: isSecureEnv ? ('lax' as const) : ('lax' as const),
        maxAge: ACCESS_TOKEN_TTL_MS,
        path: '/',
      };

      res.cookie('accessToken', result.accessToken, cookieOptions);
      res.cookie('refreshToken', result.refreshToken, cookieOptions);

      // In production, don't return tokens in response body (security)
      // In development, include tokens for cross-port setup
      if (isProduction) {
        ResponseHandler.success(res, {
          user: result.user,
          idToken: result.oktaIdToken, // Include id_token for Tanflow logout
        }, 'Authentication successful');
      } else {
        ResponseHandler.success(res, {
          user: result.user,
          accessToken: result.accessToken,
          refreshToken: result.refreshToken,
          idToken: result.oktaIdToken,
        }, 'Authentication successful');
      }
    } catch (error) {
      logger.error('Tanflow token exchange failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      ResponseHandler.error(res, 'Tanflow authentication failed', 400, errorMessage);
    }
  }

  /**
   * Refresh Tanflow access token
   * POST /api/v1/auth/tanflow/refresh
   */
  async refreshTanflowToken(req: Request, res: Response): Promise<void> {
    try {
      const refreshToken = req.body?.refreshToken;

      if (!refreshToken) {
        ResponseHandler.error(res, 'Refresh token is required', 400, 'Refresh token is required in request body');
        return;
      }

      const newAccessToken = await this.authService.refreshTanflowToken(refreshToken);

      // Set new access token in cookie
      const isProduction = process.env.NODE_ENV === 'production';
      const isUat = process.env.NODE_ENV === 'uat';
      const isSecureEnv = isProduction || isUat;

      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: isSecureEnv ? ('lax' as const) : ('lax' as const),
        maxAge: ACCESS_TOKEN_TTL_MS,
        path: '/',
      };

      res.cookie('accessToken', newAccessToken, cookieOptions);

      if (isProduction) {
        ResponseHandler.success(res, {
          message: 'Token refreshed successfully'
        }, 'Token refreshed successfully');
      } else {
        ResponseHandler.success(res, {
          accessToken: newAccessToken
        }, 'Token refreshed successfully');
      }
    } catch (error) {
      logger.error('Tanflow token refresh failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      ResponseHandler.error(res, 'Token refresh failed', 401, errorMessage);
    }
  }

  /**
   * Logout user
   * POST /api/v1/auth/logout
   * Clears all authentication cookies and tokens
   * IMPORTANT: Must use EXACT same cookie options as when setting cookies
   */
  async logout(req: Request, res: Response): Promise<void> {
    const isProduction = process.env.NODE_ENV === 'production';

    // Helper function to clear cookies with all possible option combinations
    const clearCookiesCompletely = () => {
      const isProduction = process.env.NODE_ENV === 'production';
      const isUat = process.env.NODE_ENV === 'uat';
      const isSecureEnv = isProduction || isUat;
      const cookieNames = ['accessToken', 'refreshToken'];

      // Get the EXACT options used when setting cookies (from exchangeToken)
      // These MUST match exactly: httpOnly, secure, sameSite, path
      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: 'lax' as const,
        path: '/',
      };

      logger.info('Attempting to clear cookies with options:', {
        httpOnly: cookieOptions.httpOnly,
        secure: cookieOptions.secure,
        sameSite: cookieOptions.sameSite,
        path: cookieOptions.path,
        isProduction,
      });

      // Method 1: Set expired cookie with exact same options
      // This is the most reliable method - sets cookie to expire immediately
      const expiredDate = new Date(0); // Jan 1, 1970
      cookieNames.forEach(name => {
        res.cookie(name, '', {
          httpOnly: cookieOptions.httpOnly,
          secure: cookieOptions.secure,
          sameSite: cookieOptions.sameSite,
          path: cookieOptions.path,
          expires: expiredDate,
          maxAge: 0,
        });
        logger.info(`Set expired cookie: ${name}`);
      });

      // Method 2: Use clearCookie with exact same options
      // clearCookie requires same options that were used to set the cookie
      cookieNames.forEach(name => {
        res.clearCookie(name, cookieOptions);
        logger.info(`Called clearCookie for: ${name}`);
      });

      // Method 3: Try without secure flag (for localhost/development)
      if (!isProduction) {
        cookieNames.forEach(name => {
          res.clearCookie(name, {
            httpOnly: true,
            secure: false,
            sameSite: 'lax',
            path: '/',
          });
        });
      }

      // Method 4: Try with all possible path variations
      const paths = ['/', '/api', '/api/v1'];
      paths.forEach(path => {
        cookieNames.forEach(name => {
          res.clearCookie(name, {
            httpOnly: true,
            secure: isProduction,
            sameSite: 'lax',
            path: path,
          });
        });
      });

      logger.info('Cookies clearing attempted with all methods', {
        cookieNames,
        isProduction,
        paths: ['/', '/api', '/api/v1'],
      });
    };

    try {
      // Logout should work even without authentication (to clear cookies)
      // User might be null if token was invalid/expired
      const userId = req.user?.userId || 'unknown';
      const email = req.user?.email || 'unknown';

      logger.info('User logout initiated', {
        userId,
        email,
        hasUser: !!req.user,
        hasCookies: !!req.cookies?.accessToken || !!req.cookies?.refreshToken,
        hasHeaderToken: !!req.headers.authorization,
      });

      // Clear all cookies using multiple methods
      clearCookiesCompletely();

      if (userId !== 'unknown') {
        const user = await this.authService.getUserProfile(userId);
        if (user) {
          await this.authService.updateUserProfile(userId, { sessionToken: null, lastLoginDevice: null });
        }
      }

      logger.info('User logout successful - cookies cleared', {
        userId: req.user?.userId || 'unknown',
        email: req.user?.email || 'unknown',
      });

      // Return success response
      ResponseHandler.success(res, { message: 'Logout successful, cookies cleared' }, 'Logout successful');
    } catch (error) {
      logger.error('Logout failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';

      // Even on error, try to clear cookies as last resort
      try {
        clearCookiesCompletely();
      } catch (cookieError) {
        logger.error('Error clearing cookies in catch block:', cookieError);
      }

      ResponseHandler.error(res, 'Logout failed', 500, errorMessage);
    }
  }

  /**
   * Validate token endpoint
   * GET /api/v1/auth/validate
   */
  async validateToken(req: AuthenticatedRequest, res: Response): Promise<void> {
    try {
      ResponseHandler.success(res, {
        valid: true,
        user: req.user
      }, 'Token is valid');
    } catch (error) {
      logger.error('Token validation failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      ResponseHandler.error(res, 'Token validation failed', 401, errorMessage);
    }
  }

  /**
   * Login with username and password
   * POST /api/v1/auth/login
   * 
   * This endpoint:
   * 1. Validates credentials against Okta
   * 2. Creates user in DB if they exist in Okta but not in our DB
   * 3. Returns JWT access and refresh tokens
   */
  async login(req: Request, res: Response): Promise<void> {
    try {
      logger.info('Password login request received', {
        username: req.body?.username,
        hasPassword: !!req.body?.password,
      });

      const { username, password } = validatePasswordLogin(req.body);

      const userAgent = req.headers['user-agent'] || getRequestMetadata(req).userAgent;
      const result = await this.authService.authenticateWithPassword(username, password, userAgent);

      // Log login activity
      const requestMeta = getRequestMetadata(req);
      await activityService.log({
        requestId: SYSTEM_EVENT_REQUEST_ID,
        type: 'login',
        user: {
          userId: result.user.userId,
          name: result.user.displayName || result.user.email,
          email: result.user.email
        },
        timestamp: new Date().toISOString(),
        action: 'User Login',
        details: `User logged in via username/password from ${requestMeta.ipAddress || 'unknown IP'}`,
        metadata: {
          loginMethod: 'PASSWORD',
          employeeId: result.user.employeeId,
          department: result.user.department,
          role: result.user.role
        },
        ipAddress: requestMeta.ipAddress,
        userAgent: requestMeta.userAgent,
        category: 'AUTHENTICATION',
        severity: 'INFO'
      });

      // Set cookies for web clients
      const isProduction = process.env.NODE_ENV === 'production';
      const isUat = process.env.NODE_ENV === 'uat';
      const isSecureEnv = isProduction || isUat;

      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: isSecureEnv ? 'lax' as const : 'lax' as const,
        maxAge: ACCESS_TOKEN_TTL_MS,
      };

      res.cookie('accessToken', result.accessToken, cookieOptions);

      const refreshCookieOptions = {
        ...cookieOptions,
        maxAge: REFRESH_TOKEN_TTL_MS,
      };

      res.cookie('refreshToken', result.refreshToken, refreshCookieOptions);

      logger.info('Password login successful', {
        userId: result.user.userId,
        email: result.user.email,
      });

      // Return tokens in response (for Postman/API clients)
      ResponseHandler.success(res, {
        user: result.user,
        accessToken: result.accessToken,
        refreshToken: result.refreshToken,
      }, 'Login successful');
    } catch (error) {
      logger.error('Password login failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Invalid credentials';
      ResponseHandler.error(res, 'Login failed', 401, errorMessage);
    }
  }

  /**
   * Exchange authorization code for tokens
   * POST /api/v1/auth/token-exchange
   */
  async exchangeToken(req: Request, res: Response): Promise<void> {
    try {
      logger.info('Token exchange request received', {
        body: {
          code: req.body?.code ? `${req.body.code.substring(0, 10)}...` : 'MISSING',
          redirectUri: req.body?.redirectUri,
        },
        headers: req.headers,
      });

      const { code, redirectUri } = validateTokenExchange(req.body);
      logger.info('Token exchange validation passed', { redirectUri });

      if (this.hasConsumedCode(code)) {
        ResponseHandler.error(
          res,
          'Token exchange failed',
          400,
          'RELOGIN_REQUIRED'
        );
        return;
      }
      this.markCodeConsumed(code);

      const userAgent = req.headers['user-agent'] || getRequestMetadata(req).userAgent;
      const result = await this.authService.exchangeCodeForTokens(code, redirectUri, userAgent);

      // Log login activity
      const requestMeta = getRequestMetadata(req);
      await activityService.log({
        requestId: SYSTEM_EVENT_REQUEST_ID, // Special UUID for system events
        type: 'login',
        user: {
          userId: result.user.userId,
          name: result.user.displayName || result.user.email,
          email: result.user.email
        },
        timestamp: new Date().toISOString(),
        action: 'User Login',
        details: `User logged in via token exchange from ${requestMeta.ipAddress || 'unknown IP'}`,
        metadata: {
          loginMethod: 'TOKEN_EXCHANGE',
          employeeId: result.user.employeeId,
          department: result.user.department,
          role: result.user.role
        },
        ipAddress: requestMeta.ipAddress,
        userAgent: requestMeta.userAgent,
        category: 'AUTHENTICATION',
        severity: 'INFO'
      });

      // Set cookies with httpOnly flag for security
      const isProduction = process.env.NODE_ENV === 'production';
      const isUat = process.env.NODE_ENV === 'uat';
      const isSecureEnv = isProduction || isUat;

      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: isSecureEnv ? 'lax' as const : 'lax' as const, // 'lax' for same-domain
        maxAge: ACCESS_TOKEN_TTL_MS,
      };

      res.cookie('accessToken', result.accessToken, cookieOptions);

      const refreshCookieOptions = {
        ...cookieOptions,
        maxAge: REFRESH_TOKEN_TTL_MS,
      };

      res.cookie('refreshToken', result.refreshToken, refreshCookieOptions);

      // Ensure Content-Type is set to JSON
      res.setHeader('Content-Type', 'application/json');

      logger.info('Sending token exchange response', {
        hasUser: !!result.user,
        hasAccessToken: !!result.accessToken,
        hasRefreshToken: !!result.refreshToken,
        isProduction,
      });

      // SECURITY: In production, don't return tokens in response body
      // Tokens are securely stored in httpOnly cookies
      if (isProduction) {
        ResponseHandler.success(res, {
          user: result.user,
          // idToken needed for Okta logout - stored briefly in sessionStorage
          idToken: result.oktaIdToken
        }, 'Token exchange successful');
      } else {
        // Dev: Include tokens for debugging and different-port setup
        ResponseHandler.success(res, {
          user: result.user,
          accessToken: result.accessToken,
          refreshToken: result.refreshToken,
          idToken: result.oktaIdToken
        }, 'Token exchange successful');
      }
    } catch (error) {
      logger.error('Token exchange failed:', error);
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      const normalized = String(errorMessage || '').toLowerCase();
      const isExpiredOrInvalidCode =
        normalized.includes('authorization code is invalid or has expired') ||
        normalized.includes('invalid_grant');
      if (isExpiredOrInvalidCode) {
        ResponseHandler.error(res, 'Token exchange failed', 400, 'RELOGIN_REQUIRED');
        return;
      }
      ResponseHandler.error(res, 'Token exchange failed', 400, errorMessage);
    }
  }
}
