/* eslint-disable @typescript-eslint/no-unused-vars */
import { Logger } from "../utils";
import { Disposable } from "../utils/dispose";
import * as vscode from "vscode";
import crypto from "crypto";
import {
  AUTH_NAME,
  AUTH_TIMEOUT,
  AUTH_TYPE,
  BASE_URL,
  ERRORS,
} from "./AuthConfig";

// interface SessionData {
//   id: string;
//   accessToken: string;
//   account: {
//     id: string;
//     label: string;
//   };
//   scopes: readonly string[];
// }

const createCancellationPromise = (
  cancellationToken: vscode.CancellationToken,
): Promise<never> => {
  return new Promise<never>((_, reject) => {
    cancellationToken.onCancellationRequested(() => {
      reject(new Error(ERRORS.USER_CANCELLED));
    });
  });
};
/**
 * Dedicated URI handler for OAuth callbacks
 */
export class AuthUriHandler
  extends vscode.EventEmitter<vscode.Uri>
  implements vscode.UriHandler
{
  private readonly _pendingStates = new Set<string>();
  private _codeExchangePromise:
    | Promise<{ code: string; state: string }>
    | undefined;

  public handleUri(uri: vscode.Uri): void {
    this.fire(uri);
  }

  public async waitForCallback(
    expectedState: string,
  ): Promise<{ code: string; state: string }> {
    this._pendingStates.add(expectedState);

    const timeoutPromise = new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error(ERRORS.TIMED_OUT)), AUTH_TIMEOUT),
    );

    try {
      const callbackPromise =
        this._codeExchangePromise ||
        new Promise<{ code: string; state: string }>((resolve, reject) => {
          const disposable = this.event((uri) => {
            try {
              const result = this.handleCallback(uri);
              if (result) {
                disposable.dispose();
                resolve(result);
              }
            } catch (error) {
              disposable.dispose();
              reject(error);
            }
          });
        });

      this._codeExchangePromise = callbackPromise;

      const incommingCancellationPromise =
        CodeflashAuthProvider.incommingCancellationTokenSource
          ? createCancellationPromise(
              CodeflashAuthProvider.incommingCancellationTokenSource.token,
            )
          : new Promise<never>(() => {});

      return await Promise.race([
        callbackPromise,
        timeoutPromise,
        incommingCancellationPromise,
      ]);
    } finally {
      this._pendingStates.delete(expectedState);
      this._codeExchangePromise = undefined;
    }
  }

  private handleCallback(
    uri: vscode.Uri,
  ): { code: string; state: string } | null {
    if (uri.path !== "/callback") {
      return null;
    }

    const params = new URLSearchParams(uri.query);
    const code = params.get("code");
    const state = params.get("state");

    if (!code || !state) {
      throw new Error(ERRORS.AUTH_FAILED);
    }

    if (!this._pendingStates.has(state)) {
      return null;
    }

    return { code, state };
  }
}

export class CodeflashAuthProvider
  extends Disposable
  implements vscode.AuthenticationProvider
{
  private readonly logger = new Logger("CodeflashAuthProvider");
  private readonly _sessionChangeEmitter =
    new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
  private readonly _uriHandler: AuthUriHandler;
  public static incommingCancellationTokenSource: vscode.CancellationTokenSource | null =
    null;

  // private _sessionsPromise: Promise<vscode.AuthenticationSession[]>;

  constructor(private readonly context: vscode.ExtensionContext) {
    super();

    this._uriHandler = new AuthUriHandler();

    //this._sessionsPromise = this.readSessions();

    this._disposables.push(
      vscode.authentication.registerAuthenticationProvider(
        AUTH_TYPE,
        AUTH_NAME,
        this,
        { supportsMultipleAccounts: false },
      ),
      vscode.window.registerUriHandler(this._uriHandler),
      this._sessionChangeEmitter,
      // this.context.secrets.onDidChange(() => this.checkForUpdates()),
    );
  }

  get onDidChangeSessions() {
    return this._sessionChangeEmitter.event;
  }

  /**
   * Get the existing sessions
   */
  public async getSessions(
    _scopes?: readonly string[],
    _options?: vscode.AuthenticationProviderSessionOptions,
  ): Promise<vscode.AuthenticationSession[]> {
    // this.logger.info(
    //   `Getting sessions for ${scopes?.length ? scopes.join(",") : "all scopes"}...`,
    // );

    // const sessions = await this._sessionsPromise;

    // const filteredSessions = scopes?.length
    //   ? sessions.filter((session) => this.scopesMatch(session.scopes, scopes))
    //   : sessions;

    // this.logger.info(`Got ${filteredSessions.length} session(s)`);
    // return filteredSessions;
    return Promise.resolve([]);
  }

  /**
   * Create a new auth session
   */
  public async createSession(
    scopes: readonly string[],
  ): Promise<vscode.AuthenticationSession> {
    try {
      this.logger.info("Starting authentication flow");

      const { code, codeVerifier, redirectUri } =
        await this.initiateLoginWithProgress();

      // Exchange authorization code for access token
      const apiKey = await this.exchangeCodeForToken(
        code,
        codeVerifier,
        redirectUri,
      );

      // Create session
      const session: vscode.AuthenticationSession = {
        id: this.generateSessionId(),
        accessToken: apiKey,
        account: { id: apiKey, label: apiKey },
        scopes,
      };

      // // Update stored sessions
      // await this.addSession(session);

      this.logger.info("Authentication successful");
      return session;
    } catch (error) {
      this.handleAuthError(error);
      throw error;
    }
  }

  /**
   * Remove an existing session
   */
  public async removeSession(_sessionId: string): Promise<void> {}

  // ============================================
  // Private Helper Methods
  // ============================================
  private async initiateLoginWithProgress(): Promise<{
    code: string;
    state: string;
    codeVerifier: string;
    redirectUri: string;
  }> {
    return await this.initiateLogin();
  }

  private async initiateLogin(): Promise<{
    code: string;
    state: string;
    codeVerifier: string;
    redirectUri: string;
  }> {
    const publisher = this.context.extension.packageJSON.publisher;
    const name = this.context.extension.packageJSON.name;
    const redirectUri = `${vscode.env.uriScheme}://${publisher}.${name}/callback`;
    //TODO: FIX ENCODE ISSUE
    // const externalUri = await vscode.env.asExternalUri(
    //   vscode.Uri.parse(redirectUri),
    // );

    const codeVerifier = crypto.randomBytes(32).toString("base64url");
    const codeChallenge = crypto
      .createHash("sha256")
      .update(codeVerifier)
      .digest("base64url");

    const state = crypto.randomUUID();

    await this.context.secrets.store("auth-code-verifier", codeVerifier);
    await this.context.secrets.store("auth-state", state);
    const clientId = "cf_vscode_app";
    const params = new URLSearchParams({
      response_type: "code",
      client_id: clientId,
      redirect_uri: redirectUri,
      code_challenge: codeChallenge,
      code_challenge_method: "sha256",
      state,
      originator: `codeflash_${vscode.env.appName}`,
    });

    const codeflashAuthUrl = `${BASE_URL}codeflash/auth?${params.toString()}`;

    const opened = await vscode.env.openExternal(
      vscode.Uri.parse(codeflashAuthUrl),
    );
    if (!opened) {
      throw new Error("Failed to open browser for authentication");
    }

    this.logger.info(
      "Browser opened for authentication, waiting for callback...",
    );

    try {
      // new cancellation token source for incomming auth cancellation
      CodeflashAuthProvider.incommingCancellationTokenSource?.dispose();
      CodeflashAuthProvider.incommingCancellationTokenSource =
        new vscode.CancellationTokenSource();

      const result = await this._uriHandler.waitForCallback(state);

      const savedState = await this.context.secrets.get("auth-state");
      if (result.state !== savedState) {
        throw new Error("State validation failed");
      }

      return {
        code: result.code,
        state: result.state,
        codeVerifier,
        redirectUri,
      };
    } catch (error) {
      // Clean up stored secrets on error
      await this.context.secrets.delete("auth-code-verifier");
      await this.context.secrets.delete("auth-state");
      throw error;
    }
  }

  private async exchangeCodeForToken(
    code: string,
    codeVerifier: string,
    redirectUri: string,
  ): Promise<string> {
    try {
      const response = await fetch(`${BASE_URL}/codeflash/auth/oauth/token`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          grant_type: "authorization_code",
          code,
          redirect_uri: redirectUri,
          code_verifier: codeVerifier,
          client_id: "cf_vscode_app",
        }),
      });

      if (!response.ok) {
        throw new Error(`Token exchange failed: ${response.status}`);
      }

      const data = await response.json();

      if (!data.access_token) {
        throw new Error("No access token in response");
      }

      return data.access_token;
    } catch (error) {
      if (error instanceof TypeError && error.message.includes("fetch")) {
        throw new Error(ERRORS.NETWORK_ERROR);
      }
      throw error;
    }
  }

  // private async readSessions(): Promise<vscode.AuthenticationSession[]> {
  //   try {
  //     this.logger.info("Reading sessions from storage...");
  //     const stored = await this.context.secrets.get("codeflash-sessions");

  //     if (!stored) {
  //       this.logger.info("No stored sessions found");
  //       return [];
  //     }

  //     const sessions = JSON.parse(stored) as SessionData[];
  //     this.logger.info(`Loaded ${sessions.length} session(s)`);

  //     return sessions.map(this.sessionDataToSession);
  //   } catch (error) {
  //     this.logger.error("Failed to read sessions: " + String(error));
  //     return [];
  //   }
  // }

  // private async storeSessions(
  //   sessions: vscode.AuthenticationSession[],
  // ): Promise<void> {
  //   this.logger.info(`Storing ${sessions.length} session(s)...`);
  //   this._sessionsPromise = Promise.resolve(sessions);

  //   const sessionData: SessionData[] = sessions.map((s) => ({
  //     id: s.id,
  //     accessToken: s.accessToken,
  //     account: s.account,
  //     scopes: s.scopes,
  //   }));

  //   await this.context.secrets.store(
  //     "codeflash-sessions",
  //     JSON.stringify(sessionData),
  //   );
  //   this.logger.info("Sessions stored successfully");
  // }

  // private async addSession(
  //   session: vscode.AuthenticationSession,
  // ): Promise<void> {
  //   const sessions = await this._sessionsPromise;

  //   // Remove any existing session (single account mode)
  //   const removed = sessions.splice(0, sessions.length);
  //   sessions.push(session);

  //   await this.storeSessions(sessions);

  //   this._sessionChangeEmitter.fire({
  //     added: [session],
  //     removed,
  //     changed: [],
  //   });
  // }

  // private async checkForUpdates(): Promise<void> {
  //   const previousSessions = await this._sessionsPromise;
  //   this._sessionsPromise = this.readSessions();
  //   const storedSessions = await this._sessionsPromise;

  //   const added: vscode.AuthenticationSession[] = [];
  //   const removed: vscode.AuthenticationSession[] = [];

  //   storedSessions.forEach((session) => {
  //     if (!previousSessions.some((s) => s.id === session.id)) {
  //       this.logger.info("Adding session found in storage");
  //       added.push(session);
  //     }
  //   });

  //   previousSessions.forEach((session) => {
  //     if (!storedSessions.some((s) => s.id === session.id)) {
  //       this.logger.info("Removing session no longer in storage");
  //       removed.push(session);
  //     }
  //   });

  //   if (added.length || removed.length) {
  //     this._sessionChangeEmitter.fire({ added, removed, changed: [] });
  //   }
  // }

  // private sessionDataToSession(
  //   data: SessionData,
  // ): vscode.AuthenticationSession {
  //   return {
  //     id: data.id,
  //     accessToken: data.accessToken,
  //     account: data.account,
  //     scopes: data.scopes,
  //   };
  // }

  private generateSessionId(): string {
    return crypto
      .getRandomValues(new Uint32Array(2))
      .reduce((prev, curr) => prev + curr.toString(16), "");
  }

  private handleAuthError(error: unknown): void {
    const errorMessage = error instanceof Error ? error.message : String(error);

    if (errorMessage === ERRORS.USER_CANCELLED) {
      this.logger.info("User cancelled authentication");
      return;
    }

    if (errorMessage === ERRORS.TIMED_OUT) {
      this.logger.debug("Authentication timed out");
      return;
    }

    if (errorMessage === ERRORS.NETWORK_ERROR) {
      vscode.window.showErrorMessage(
        "Unable to connect to CodeFlash. Please check your internet connection.",
      );
      this.logger.error("Network error during authentication");
      return;
    }

    this.logger.error("Authentication failed: " + errorMessage);
  }
}
