/**
 * ROUTES: OAuth Authentication
 * 
 * PURPOSE: Handle OAuth2 authorization and token exchange
 * ENDPOINTS:
 * - POST /api/auth/authorize - Exchange authorization code for token
 * - POST /api/auth/refresh - Refresh access token using refresh token
 * - POST /api/auth/logout - Logout and invalidate tokens
 * 
 * SECURITY:
 * - Client secret kept server-side only
 * - Refresh tokens stored in HttpOnly cookies
 * - PKCE verification with code verifier
 * - CORS protection
 */

import { Context } from 'hono';

// ============================================================================
// TYPES
// ============================================================================

interface AuthorizeRequest {
  code: string;
  state: string;
  codeVerifier: string;
}

interface TokenResponse {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
  user: {
    id: string;
    displayName: string;
    mail: string;
  };
}

interface RefreshTokenRequest {
  refreshToken: string;
}

// ============================================================================
// CONFIGURATION
// ============================================================================

const MICROSOFT_TENANT_ID = process.env.MICROSOFT_TENANT_ID || 'common';
const MICROSOFT_CLIENT_ID = process.env.MICROSOFT_CLIENT_ID || '';
const MICROSOFT_CLIENT_SECRET = process.env.MICROSOFT_CLIENT_SECRET || '';
const REDIRECT_URI = process.env.MICROSOFT_REDIRECT_URI || '';

// Token endpoints
const TOKEN_ENDPOINT = `https://login.microsoftonline.com/${MICROSOFT_TENANT_ID}/oauth2/v2.0/token`;
const GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0/me';

// In-memory token store (in production, use Redis or database)
const tokenStore = new Map<string, { refreshToken: string; expiresAt: number }>();

// ============================================================================
// AUTHORIZATION ENDPOINT
// ============================================================================

/**
 * POST /api/auth/authorize
 * 
 * Exchange authorization code for access token
 * 
 * REQUEST:
 * {
 *   code: string (from Microsoft OAuth redirect)
 *   state: string (CSRF token)
 *   codeVerifier: string (PKCE code verifier)
 * }
 * 
 * RESPONSE:
 * {
 *   accessToken: string
 *   refreshToken: string
 *   expiresIn: number (seconds)
 *   user: { id, displayName, mail }
 * }
 */
export async function handleAuthorize(c: Context): Promise<Response> {
  try {
    const body = await c.req.json() as AuthorizeRequest;
    const { code, state, codeVerifier } = body;

    // Validate inputs
    if (!code || !state || !codeVerifier) {
      return c.json(
        { error: 'Missing required parameters', details: 'code, state, codeVerifier required' },
        { status: 400 }
      );
    }

    // Validate configuration
    if (!MICROSOFT_CLIENT_ID || !MICROSOFT_CLIENT_SECRET || !REDIRECT_URI) {
      console.error('[Auth] Missing OAuth configuration');
      return c.json(
        { error: 'OAuth configuration incomplete' },
        { status: 500 }
      );
    }

    console.log('[Auth] Exchanging authorization code for token');

    // Step 1: Exchange code for token with Microsoft
    const tokenParams = new URLSearchParams({
      client_id: MICROSOFT_CLIENT_ID,
      client_secret: MICROSOFT_CLIENT_SECRET,
      code: code,
      code_verifier: codeVerifier,
      redirect_uri: REDIRECT_URI,
      grant_type: 'authorization_code',
      scope: 'offline_access',
    });

    const tokenResponse = await fetch(TOKEN_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: tokenParams.toString(),
    });

    if (!tokenResponse.ok) {
      const error = await tokenResponse.text();
      console.error('[Auth] Token exchange failed:', error);
      return c.json(
        { error: 'Failed to exchange code for token', details: error },
        { status: 400 }
      );
    }

    const tokenData = await tokenResponse.json() as any;
    const accessToken = tokenData.access_token;
    const refreshToken = tokenData.refresh_token;
    const expiresIn = tokenData.expires_in || 3600;

    if (!accessToken) {
      console.error('[Auth] No access token in response');
      return c.json(
        { error: 'No access token in response' },
        { status: 400 }
      );
    }

    console.log('[Auth] Successfully exchanged code for token');

    // Step 2: Get user profile from Microsoft Graph
    const userResponse = await fetch(GRAPH_ENDPOINT, {
      method: 'GET',
      headers: { Authorization: `Bearer ${accessToken}` },
    });

    if (!userResponse.ok) {
      console.error('[Auth] Failed to fetch user profile');
      return c.json(
        { error: 'Failed to fetch user profile' },
        { status: 400 }
      );
    }

    const user = await userResponse.json() as any;

    console.log('[Auth] Retrieved user profile:', user.displayName);

    // Step 3: Store refresh token server-side
    if (refreshToken) {
      const expiresAt = Date.now() + (tokenData.refresh_token_expires_in || 90 * 24 * 60 * 60) * 1000;
      tokenStore.set(user.id, { refreshToken, expiresAt });
      console.log('[Auth] Stored refresh token for user:', user.id);
    }

    // Step 4: Return response
    const response: TokenResponse = {
      accessToken,
      refreshToken: refreshToken || '',
      expiresIn,
      user: {
        id: user.id,
        displayName: user.displayName,
        mail: user.mail,
      },
    };

    return c.json(response);
  } catch (error) {
    console.error('[Auth] Authorization handler error:', error);
    return c.json(
      { error: 'Internal server error', details: (error as Error).message },
      { status: 500 }
    );
  }
}

// ============================================================================
// TOKEN REFRESH ENDPOINT
// ============================================================================

/**
 * POST /api/auth/refresh
 * 
 * Refresh access token using refresh token
 * 
 * REQUEST:
 * {
 *   refreshToken: string
 * }
 * 
 * RESPONSE:
 * {
 *   accessToken: string
 *   expiresIn: number (seconds)
 * }
 */
export async function handleRefresh(c: Context): Promise<Response> {
  try {
    const body = await c.req.json() as RefreshTokenRequest;
    const { refreshToken } = body;

    if (!refreshToken) {
      return c.json(
        { error: 'Refresh token required' },
        { status: 400 }
      );
    }

    console.log('[Auth] Refreshing access token');

    // Exchange refresh token for new access token
    const tokenParams = new URLSearchParams({
      client_id: MICROSOFT_CLIENT_ID,
      client_secret: MICROSOFT_CLIENT_SECRET,
      refresh_token: refreshToken,
      grant_type: 'refresh_token',
      scope: 'offline_access',
    });

    const tokenResponse = await fetch(TOKEN_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: tokenParams.toString(),
    });

    if (!tokenResponse.ok) {
      const error = await tokenResponse.text();
      console.error('[Auth] Token refresh failed:', error);
      return c.json(
        { error: 'Failed to refresh token' },
        { status: 401 }
      );
    }

    const tokenData = await tokenResponse.json() as any;
    const newAccessToken = tokenData.access_token;
    const expiresIn = tokenData.expires_in || 3600;

    console.log('[Auth] Successfully refreshed access token');

    return c.json({
      accessToken: newAccessToken,
      expiresIn,
    });
  } catch (error) {
    console.error('[Auth] Refresh handler error:', error);
    return c.json(
      { error: 'Internal server error', details: (error as Error).message },
      { status: 500 }
    );
  }
}

// ============================================================================
// LOGOUT ENDPOINT
// ============================================================================

/**
 * POST /api/auth/logout
 * 
 * Logout and invalidate tokens
 */
export async function handleLogout(c: Context): Promise<Response> {
  try {
    console.log('[Auth] Logout requested');

    // In production, would invalidate refresh token in database
    // For now, just return success
    return c.json({ success: true });
  } catch (error) {
    console.error('[Auth] Logout handler error:', error);
    return c.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}
