import { CODEX_CONFIG } from "../constants/oauth";

/**
 * OpenAI Codex Auth Info embedded in id_token JWT
 * The JWT claims contain a custom claim at "https://api.openai.com/auth"
 */
interface CodexAuthInfo {
  chatgpt_account_id: string;
  chatgpt_plan_type: string;
  chatgpt_user_id: string;
  user_id: string;
  organizations: Array<{
    id: string;
    is_default: boolean;
    role: string;
    title: string;
  }>;
}

/**
 * Decode base64 string with proper UTF-8 handling.
 * atob() doesn't handle multi-byte UTF-8 characters correctly.
 */
function base64Decode(str: string): string {
  // Add padding if necessary
  let base64 = str;
  switch (base64.length % 4) {
    case 2:
      base64 += "==";
      break;
    case 3:
      base64 += "=";
      break;
  }

  // Replace URL-safe characters with standard base64 characters
  base64 = base64.replace(/-/g, "+").replace(/_/g, "/");

  // Decode using atob, then handle UTF-8
  const binary = atob(base64);

  // Convert binary string to bytes, then to UTF-8 string
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }

  // Use TextDecoder for proper UTF-8 decoding
  return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
}

/**
 * Parse the id_token JWT to extract Codex-specific auth information.
 * The workspace selection is embedded in the JWT after OAuth completion.
 *
 * Note: The OAuth flow already verified this token with OpenAI's server.
 * We only extract metadata (workspace info) from the already-validated token.
 */
function parseIdToken(idToken: string): { email: string | null; authInfo: CodexAuthInfo | null } {
  try {
    const parts = idToken.split(".");
    if (parts.length !== 3) {
      return { email: null, authInfo: null };
    }

    // Decode payload with proper UTF-8 handling
    const decoded = JSON.parse(base64Decode(parts[1]));

    const email = decoded.email || null;

    // Extract Codex auth info from custom claim
    const authInfo = decoded["https://api.openai.com/auth"] || null;

    return { email, authInfo };
  } catch (e) {
    return { email: null, authInfo: null };
  }
}

export const codex = {
  config: CODEX_CONFIG,
  flowType: "authorization_code_pkce",
  fixedPort: 1455,
  callbackPath: "/auth/callback",

  buildAuthUrl: (config, redirectUri, state, codeChallenge) => {
    const params = {
      response_type: "code",
      client_id: config.clientId,
      redirect_uri: redirectUri,
      scope: config.scope,
      code_challenge: codeChallenge,
      code_challenge_method: config.codeChallengeMethod,
      ...config.extraParams,
      state: state,
    };
    const queryString = Object.entries(params)
      .map(([key, value]) => `${key}=${encodeURIComponent(value as string)}`)
      .join("&");
    return `${config.authorizeUrl}?${queryString}`;
  },

  exchangeToken: async (config, code, redirectUri, codeVerifier) => {
    const response = await fetch(config.tokenUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json",
      },
      body: new URLSearchParams({
        grant_type: "authorization_code",
        client_id: config.clientId,
        code: code,
        redirect_uri: redirectUri,
        code_verifier: codeVerifier,
      }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Token exchange failed: ${error}`);
    }

    return await response.json();
  },

  /**
   * Post-exchange hook: Parse id_token to extract workspace info.
   * The workspace selected by the user during OAuth is embedded in the id_token.
   */
  postExchange: async (tokens) => {
    if (!tokens.id_token) {
      return { authInfo: null };
    }

    const { authInfo } = parseIdToken(tokens.id_token);
    return { authInfo };
  },

  mapTokens: (tokens, extra) => {
    // Parse id_token for email and auth info
    let email = null;
    let authInfo = extra?.authInfo || null;

    if (tokens.id_token) {
      const parsed = parseIdToken(tokens.id_token);
      email = parsed.email;
      // Use authInfo from postExchange if available, otherwise from parsing
      if (!authInfo && parsed.authInfo) {
        authInfo = parsed.authInfo;
      }
    }

    // Determine the correct workspace to use
    //
    // IMPORTANT: A user can have both Team and Personal workspaces.
    // The JWT's chatgpt_account_id may not always reflect the workspace
    // the user selected during OAuth. We need to be smart about selection.
    //
    // Selection logic:
    // 1. If plan_type indicates team/business, use chatgpt_account_id
    // 2. If plan_type is "free" but organizations has team workspace, use team
    // 3. Otherwise use chatgpt_account_id as fallback
    let workspaceId = authInfo?.chatgpt_account_id || null;
    let planType = (authInfo?.chatgpt_plan_type || "").toLowerCase();

    // Check if we should use a team workspace instead
    const organizations = authInfo?.organizations || [];
    if (organizations.length > 0) {
      // Find team/business workspace (non-default usually means team)
      const teamOrg = organizations.find((org) => {
        const title = (org.title || "").toLowerCase();
        const role = (org.role || "").toLowerCase();
        // Team workspaces typically have role like "member" or "admin" and non-personal titles
        return (
          !org.is_default &&
          (title.includes("team") ||
            title.includes("business") ||
            title.includes("workspace") ||
            title.includes("org") ||
            role === "admin" ||
            role === "member")
        );
      });

      // If user's plan_type is "team" or we found a team org, prefer it
      if (planType.includes("team") || planType.includes("chatgptteam")) {
        // User authenticated via Team, use the chatgpt_account_id from JWT
      } else if (teamOrg && (planType === "free" || planType === "")) {
        // User has a team org but plan_type shows free - use team org instead
        workspaceId = teamOrg.id;
        planType = "team";
      }
    }

    const providerSpecificData = {
      workspaceId,
      workspacePlanType: planType,
      // Also store the full authInfo for future reference
      chatgptUserId: authInfo?.chatgpt_user_id || null,
      organizations: organizations.length > 0 ? organizations : null,
    };

    return {
      accessToken: tokens.access_token,
      refreshToken: tokens.refresh_token,
      idToken: tokens.id_token,
      expiresIn: tokens.expires_in,
      email,
      // Persist workspace binding to prevent fallback to wrong workspace
      providerSpecificData,
    };
  },
};
