import { CURSOR_CONFIG } from "../constants/oauth";
import { getCursorUserAgent } from "@omniroute/open-sse/config/providerHeaderProfiles.ts";

/**
 * Cursor IDE OAuth Service
 * Supports Import Token method from Cursor IDE's local SQLite database
 *
 * Token Location:
 * - Linux: ~/.config/Cursor/User/globalStorage/state.vscdb
 * - macOS: /Users/<user>/Library/Application Support/Cursor/User/globalStorage/state.vscdb
 * - Windows: %APPDATA%\Cursor\User\globalStorage\state.vscdb
 *
 * Database Keys:
 * - cursorAuth/accessToken: The access token
 * - storage.serviceMachineId: Machine ID for checksum
 */

export class CursorService {
  config: any;

  constructor() {
    this.config = CURSOR_CONFIG;
  }

  /**
   * Generate Cursor checksum (jyh cipher)
   * Algorithm: XOR timestamp bytes with rolling key (initial 165), then base64 encode
   * Format: {encoded_timestamp},{machineId}
   */
  generateChecksum(machineId: string) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    let key = 165;
    const encoded = [];

    for (let i = 0; i < timestamp.length; i++) {
      const charCode = timestamp.charCodeAt(i);
      encoded.push(charCode ^ key);
      key = (key + charCode) & 0xff; // Rolling key update
    }

    const base64Encoded = Buffer.from(encoded).toString("base64");
    return `${base64Encoded},${machineId}`;
  }

  /**
   * Build request headers for Cursor API
   */
  buildHeaders(accessToken: string, machineId: string, ghostMode = false) {
    const checksum = this.generateChecksum(machineId);

    return {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/connect+proto",
      "Connect-Protocol-Version": "1",
      "User-Agent": getCursorUserAgent(this.config.clientVersion),
      "x-cursor-client-version": this.config.clientVersion,
      "x-cursor-client-type": this.config.clientType,
      "x-cursor-client-os": this.detectOS(),
      "x-cursor-client-arch": this.detectArch(),
      "x-cursor-client-device-type": "desktop",
      "x-cursor-user-agent": getCursorUserAgent(this.config.clientVersion),
      "x-cursor-checksum": checksum,
      "x-ghost-mode": ghostMode ? "true" : "false",
    };
  }

  /**
   * Detect OS for headers
   */
  detectOS() {
    if (typeof process !== "undefined") {
      const platform = process.platform;
      if (platform === "win32") return "windows";
      if (platform === "darwin") return "macos";
      return "linux";
    }
    return "linux";
  }

  /**
   * Detect architecture for headers
   */
  detectArch() {
    if (typeof process !== "undefined") {
      const arch = process.arch;
      if (arch === "x64") return "x86_64";
      if (arch === "arm64") return "aarch64";
      return arch;
    }
    return "x86_64";
  }

  /**
   * Validate and import token from Cursor IDE or cursor-agent CLI.
   * Note: We skip API validation because Cursor API uses complex protobuf format.
   * Token will be validated when actually used for requests.
   * @param {string} accessToken - Access token from state.vscdb or auth.json
   * @param {string} [machineId] - Machine ID from state.vscdb (optional for cursor-agent imports)
   */
  async validateImportToken(accessToken: string, machineId?: string) {
    // Basic validation
    if (!accessToken || typeof accessToken !== "string") {
      throw new Error("Access token is required");
    }

    // Token format validation (Cursor tokens are typically long strings)
    if (accessToken.length < 50) {
      throw new Error("Invalid token format. Token appears too short.");
    }

    // Machine ID format validation (only if provided — cursor-agent imports don't have one)
    if (machineId) {
      const uuidRegex = /^[a-f0-9-]{32,}$/i;
      if (!uuidRegex.test(machineId.replace(/-/g, ""))) {
        throw new Error("Invalid machine ID format. Expected UUID format.");
      }
    }

    // Note: We don't validate against API because Cursor uses complex protobuf.
    // Token will be validated when used for actual requests.

    return {
      accessToken,
      machineId: machineId || null,
      expiresIn: 86400, // Cursor tokens typically last 24 hours
      authMethod: machineId ? "imported" : "cursor-agent",
    };
  }

  /**
   * Extract user info from token if possible
   * Cursor tokens may contain encoded user info
   */
  extractUserInfo(accessToken: string) {
    try {
      // Try to decode as JWT
      const parts = accessToken.split(".");
      if (parts.length === 3) {
        let payload = parts[1];
        while (payload.length % 4) {
          payload += "=";
        }
        const decoded = JSON.parse(
          Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString()
        );
        const email =
          typeof decoded.email === "string" && decoded.email.includes("@") ? decoded.email : null;
        return {
          email,
          userId: decoded.sub || decoded.user_id,
        };
      }
    } catch {
      // Token is not a JWT, that's okay
    }

    return null;
  }

  /**
   * Fetch real user profile from cursor.com using the same WorkOS-session cookie
   * format that powers the dashboard. Returns null on any failure so the import
   * flow can fall back to whatever it can extract from the JWT.
   */
  async fetchUserInfo(
    accessToken: string,
    userId: string
  ): Promise<{ email: string | null; name: string | null; sub: string | null } | null> {
    if (!accessToken || !userId) return null;
    try {
      const response = await fetch("https://cursor.com/api/auth/me", {
        method: "GET",
        redirect: "manual",
        headers: {
          Cookie: `WorkosCursorSessionToken=${userId}::${accessToken}`,
          Origin: "https://cursor.com",
          Referer: "https://cursor.com/dashboard",
          Accept: "application/json",
          "User-Agent": getCursorUserAgent(this.config.clientVersion),
        },
      });

      if (!response.ok) return null;
      const data = (await response.json()) as Record<string, unknown>;
      return {
        email: typeof data.email === "string" ? data.email : null,
        name: typeof data.name === "string" ? data.name : null,
        sub: typeof data.sub === "string" ? data.sub : null,
      };
    } catch {
      return null;
    }
  }

  /**
   * Get token storage path instructions for user
   */
  getTokenStorageInstructions() {
    return {
      title: "How to get your Cursor token",
      steps: [
        "1. Open Cursor IDE and make sure you're logged in",
        "2. Find the state.vscdb file:",
        `   - Linux: ${this.config.tokenStoragePaths.linux}`,
        `   - macOS: ${this.config.tokenStoragePaths.macos}`,
        `   - Windows: ${this.config.tokenStoragePaths.windows}`,
        "3. Open the database with SQLite browser or CLI:",
        "   sqlite3 state.vscdb \"SELECT value FROM itemTable WHERE key='cursorAuth/accessToken'\"",
        "4. Also get the machine ID:",
        "   sqlite3 state.vscdb \"SELECT value FROM itemTable WHERE key='storage.serviceMachineId'\"",
        "5. Paste both values in the form below",
      ],
      alternativeMethod: [
        "Or use this one-liner to get both values:",
        "sqlite3 state.vscdb \"SELECT key, value FROM itemTable WHERE key IN ('cursorAuth/accessToken', 'storage.serviceMachineId')\"",
      ],
    };
  }
}
