/**
 * Authentication API Service
 * Handles communication with backend auth endpoints
 */

import axios, { AxiosInstance } from 'axios';
import { TokenManager } from '../utils/tokenManager';
import { toast } from 'sonner';
import { tanflowLogout } from './tanflowAuth';

/**
 * Perform a formal logout from Okta by redirecting the browser
 * This clears the SSO session on the Okta side
 */
export function oktaLogout(idToken?: string): void {
  const oktaDomain = import.meta.env.VITE_OKTA_DOMAIN || '{{IDP_DOMAIN}}';
  // Use the exact whitelisted login callback URI without query params to avoid mismatch errors
  const redirectUri = `${window.location.origin}/login/callback`;
  
  if (idToken) {
    // Persist logout flag in sessionStorage before redirecting 
    // This allows AuthContext to detect the return from logout without query params 
    sessionStorage.setItem('__logout_type__', 'okta');
    
    // Standard OIDC logout redirect
    const logoutUrl = `${oktaDomain}/oauth2/default/v1/logout?id_token_hint=${idToken}&post_logout_redirect_uri=${encodeURIComponent(redirectUri)}`;
    console.log('🚪 Initiating Okta logout redirect to callback (clean URI)');
    window.location.href = logoutUrl;
  } else {
    // Fallback: redirect to callback with logout flag in sessionStorage
    sessionStorage.setItem('__logout_type__', 'okta');
    console.log('🚪 No id_token for Okta logout, redirecting to callback');
    window.location.href = redirectUri;
  }
}

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';

// Create axios instance with default config
const apiClient: AxiosInstance = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: true, // Important for cookie-based auth in localhost
});

// Request interceptor to add access token
// In production: No header needed - httpOnly cookies are sent automatically via withCredentials
// In development: Add Authorization header from localStorage
apiClient.interceptors.request.use(
  (config) => {
    // FormData: do not set Content-Type so the browser sets multipart/form-data with boundary
    if (config.data instanceof FormData) {
      const h = config.headers as Record<string, unknown> & { common?: Record<string, unknown>; post?: Record<string, unknown> };
      delete h['Content-Type'];
      if (h.common && typeof h.common === 'object') delete h.common['Content-Type'];
      if (h.post && typeof h.post === 'object') delete h.post['Content-Type'];
    }

    const isProduction = import.meta.env.PROD || import.meta.env.MODE === 'production';

    if (!isProduction) {
      // Dev: Get token from localStorage and add to header
      const token = TokenManager.getAccessToken();
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
    }
    // Prod: Cookies handle authentication automatically

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Response interceptor to handle token refresh and connection errors
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    // Handle connection errors gracefully in development
    if (error.code === 'ERR_NETWORK' || error.code === 'ECONNREFUSED' || error.message?.includes('ERR_CONNECTION_REFUSED')) {
      const isDevelopment = import.meta.env.DEV || import.meta.env.MODE === 'development';

      if (isDevelopment) {
        // In development, log a helpful message instead of spamming console
        console.warn(`[API] Backend not reachable at ${API_BASE_URL}. Make sure the backend server is running on port 5000.`);
        // Don't throw - let the calling code handle it gracefully
        return Promise.reject({
          ...error,
          isConnectionError: true,
          message: 'Backend server is not reachable. Please ensure the backend is running on port 5000.'
        });
      }
    }

    // If error is 401
    if (error.response?.status === 401) {
      // Check for concurrent session logout specifically
      if (error.response?.data?.errorCode === 'SESSION_SUPERSEDED') {
        const idToken = TokenManager.getIdToken();
        const authProvider = sessionStorage.getItem('auth_provider') || (idToken?.includes('tanflow') ? 'tanflow' : 'okta');

        // Set error state in TokenManager to stop background refreshes
        TokenManager.setAuthError('SESSION_SUPERSEDED');
        
        // Show the toast immediately
        toast.error("You have been logged out because an active session was detected from another device.", { 
          duration: 2000,
          id: 'session-superseded-toast' 
        });

        // Delay sets flags and redirect so user can read the toast before UI state clears
        setTimeout(async () => {
          // Set flags JUST BEFORE redirect to ensure AuthContext only picks them up on return/reload
          sessionStorage.setItem('__logout_in_progress__', 'true');
          sessionStorage.setItem('__force_logout__', 'true');
          // IdP Logout FIRST as requested (clears SSO session)
          // Note: Backend logout will be handled by AuthContext on return redirect
          if (authProvider === 'tanflow' && idToken) {
            tanflowLogout(idToken);
          } else {
            oktaLogout(idToken || undefined);
          }
        }, 1000);

        return Promise.reject(error);
      }

      // Handle token refresh if not already retrying
      if (!originalRequest._retry) {
        originalRequest._retry = true;
        const isProduction = import.meta.env.PROD || import.meta.env.MODE === 'production';

        try {
          // Attempt to refresh token
          const refreshToken = TokenManager.getRefreshToken();

          if (!isProduction && !refreshToken) {
            throw new Error('No refresh token available');
          }

          const response = await axios.post(
            `${API_BASE_URL}/auth/refresh`,
            isProduction ? {} : { refreshToken },
            { withCredentials: true }
          );

          const responseData = response.data.data || response.data;
          const accessToken = responseData.accessToken;

          if (!isProduction && accessToken) {
            TokenManager.setAccessToken(accessToken);
            originalRequest.headers.Authorization = `Bearer ${accessToken}`;
          }

          // Retry the original request
          return apiClient(originalRequest);
        } catch (refreshError) {
          // Refresh failed, clear tokens and redirect to login
          TokenManager.clearAll();
          window.location.href = '/';
          return Promise.reject(refreshError);
        }
      }
    }

    return Promise.reject(error);
  }
);

export interface TokenExchangeResponse {
  user: {
    userId: string;
    employeeId: string;
    email: string;
    firstName: string;
    lastName: string;
    displayName: string;
    department?: string;
    designation?: string;
    role: 'USER' | 'MANAGEMENT' | 'ADMIN';
  };
  accessToken: string;
  refreshToken: string;
  idToken?: string; // ID token from Okta for logout
}

export interface RefreshTokenResponse {
  accessToken: string;
}

/**
 * Login with username and password (e.g. local dealer TESTREFLOW).
 * Stores user + tokens the same way as token exchange.
 */
export async function passwordLogin(username: string, password: string): Promise<TokenExchangeResponse> {
  const response = await apiClient.post<{ data?: TokenExchangeResponse }>(
    '/auth/login',
    { username, password },
    { withCredentials: true }
  );
  const data = response.data as any;
  const result = data.data || data;
  if (result.user) TokenManager.setUserData(result.user);
  if (result.accessToken && result.refreshToken) {
    TokenManager.setAccessToken(result.accessToken);
    TokenManager.setRefreshToken(result.refreshToken);
  }
  return result;
}

/**
 * Exchange authorization code for tokens (localhost only)
 */
export async function exchangeCodeForTokens(
  code: string,
  redirectUri: string
): Promise<TokenExchangeResponse> {
  try {
    const response = await apiClient.post<TokenExchangeResponse>(
      '/auth/token-exchange',
      {
        code,
        redirectUri,
      },
      {
        responseType: 'json', // Explicitly set response type to JSON
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      }
    );

    // Check if response is an array (buffer issue)
    if (Array.isArray(response.data)) {
      console.error('❌ Response is an array (buffer issue):', {
        arrayLength: response.data.length,
        firstFew: response.data.slice(0, 10),
        rawResponse: response,
      });
      throw new Error('Invalid response format: received array instead of JSON. Check Content-Type header.');
    }

    const data = response.data as any;
    const result = data.data || data;

    // Store user data (always available)
    if (result.user) {
      TokenManager.setUserData(result.user);
    }

    // Store ID token if available (needed for Okta logout)
    if (result.idToken) {
      TokenManager.setIdToken(result.idToken);
    }

    // SECURITY: In production, tokens are ONLY in httpOnly cookies (not in response body)
    // In development, backend returns tokens for cross-port setup
    if (result.accessToken && result.refreshToken) {
      // Dev mode: Backend returned tokens, store them
      TokenManager.setAccessToken(result.accessToken);
      TokenManager.setRefreshToken(result.refreshToken);
    }
    // Prod mode: No tokens in response - they're in httpOnly cookies
    // TokenManager.setAccessToken/setRefreshToken are no-ops in production anyway

    return result;
  } catch (error: any) {
    console.error('❌ Token exchange failed:', {
      message: error.message,
      response: error.response?.data,
      status: error.response?.status,
      code: code ? `${code.substring(0, 10)}...` : 'MISSING',
      redirectUri,
    });
    throw error;
  }
}

/**
 * Refresh access token using refresh token
 * 
 * PRODUCTION: Refresh token is in httpOnly cookie, sent automatically
 * DEVELOPMENT: Refresh token from localStorage
 */
export async function refreshAccessToken(): Promise<string> {
  const isProduction = import.meta.env.PROD || import.meta.env.MODE === 'production';

  // In development, check for refresh token in localStorage
  if (!isProduction) {
    const refreshToken = TokenManager.getRefreshToken();
    if (!refreshToken) {
      throw new Error('No refresh token available');
    }
  }

  // In production, httpOnly cookie with refresh token will be sent automatically
  // In development, we send the refresh token in the body
  const body = isProduction ? {} : { refreshToken: TokenManager.getRefreshToken() };

  const response = await apiClient.post<RefreshTokenResponse>('/auth/refresh', body);

  const data = response.data as any;
  const accessToken = data.data?.accessToken || data.accessToken;

  // In development mode, store the token
  if (!isProduction && accessToken) {
    TokenManager.setAccessToken(accessToken);
    return accessToken;
  }

  // In production mode, token is set via httpOnly cookie by the backend
  // Return a placeholder to indicate success
  if (isProduction && (data.success !== false)) {
    return 'cookie-based-auth';
  }

  throw new Error('Failed to refresh token');
}

/**
 * Get current user profile
 */
export async function getCurrentUser() {
  const response = await apiClient.get('/auth/me');
  const data = response.data as any;
  return data.data || data;
}

/**
 * Logout user
 * IMPORTANT: This endpoint MUST clear httpOnly cookies set by backend
 * Note: TokenManager.clearAll() is called in AuthContext.logout() 
 * We don't call it here to avoid double clearing
 */
export async function logout(): Promise<void> {
  try {
    // Use axios directly to avoid interceptor recursion
    await axios.post(`${API_BASE_URL}/auth/logout`, {}, {
      withCredentials: true, // Ensure cookies are sent with request
    });
  } catch (error: any) {
    console.error('📡 Logout API error:', error);
    console.error('📡 Error details:', {
      message: error.message,
      status: error.response?.status,
      data: error.response?.data,
    });
    // Even if API call fails, cookies might still be cleared
    // Don't throw - let the caller handle cleanup
  }
}

export default apiClient;

