import * as vscode from "vscode";
import * as http from "http";
import { SecretStore } from "./secretStore";

/**
 * Authentication flow helpers for MCP server providers.
 *
 * Supports:
 * - Browser-based OAuth2 / SSO (opens browser, captures redirect)
 * - Token / PAT manual entry (stored via SecretStorage)
 * - API Key input (stored via SecretStorage)
 *
 * All captured credentials go through VS Code's SecretStorage API
 * so they are encrypted at rest via the OS keychain.
 */

interface AuthResult {
  mode: string;
  serverId: string;
  success: boolean;
  tokenType?: string;
}

export class AuthFlowService {
  constructor(private readonly _secretStore: SecretStore) {}

  /**
   * Run the appropriate auth flow for a server based on its auth mode.
   */
  async authenticate(
    serverId: string,
    serverName: string,
    authMode: string,
    authUrl?: string
  ): Promise<AuthResult> {
    switch (authMode) {
      case "browser":
        return this._browserAuth(serverId, serverName, authUrl);
      case "token":
      case "api_key":
        return this._tokenAuth(serverId, serverName, authMode);
      case "oauth2":
        return this._oauth2Auth(serverId, serverName, authUrl);
      default:
        return this._tokenAuth(serverId, serverName, authMode);
    }
  }

  /**
   * Browser-based SSO: open the platform's login page in the default
   * browser, start a local HTTP server to capture the redirect callback,
   * and store the received token.
   */
  private async _browserAuth(
    serverId: string,
    serverName: string,
    authUrl?: string
  ): Promise<AuthResult> {
    if (!authUrl) {
      const url = await vscode.window.showInputBox({
        title: `Authenticate: ${serverName}`,
        prompt: "Enter the platform login URL",
        placeHolder: "https://login.platform.example.com/authorize",
      });
      if (!url) {
        return { mode: "browser", serverId, success: false };
      }
      authUrl = url;
    }

    // Start a temporary local HTTP server to receive the redirect
    const port = await this._findFreePort();
    const redirectUri = `http://localhost:${port}/callback`;

    // Build the full auth URL with redirect
    const separator = authUrl.includes("?") ? "&" : "?";
    const fullAuthUrl = `${authUrl}${separator}redirect_uri=${encodeURIComponent(redirectUri)}`;

    // Show progress while waiting for the browser flow
    const result = await vscode.window.withProgress(
      {
        location: vscode.ProgressLocation.Notification,
        title: `Authenticating with ${serverName}...`,
        cancellable: true,
      },
      async (_progress, cancelToken) => {
        return new Promise<AuthResult>((resolve) => {
          const server = http.createServer(async (req, res) => {
            if (req.url?.startsWith("/callback")) {
              const url = new URL(req.url, `http://localhost:${port}`);
              const token =
                url.searchParams.get("token") ||
                url.searchParams.get("access_token") ||
                url.searchParams.get("code");

              if (token) {
                await this._secretStore.setServerSecret(
                  serverId,
                  "auth_token",
                  token
                );
                res.writeHead(200, { "Content-Type": "text/html" });
                res.end(
                  "<html><body><h2>Authentication Successful!</h2><p>You can close this tab and return to VS Code.</p></body></html>"
                );
                server.close();
                resolve({
                  mode: "browser",
                  serverId,
                  success: true,
                  tokenType: "auth_token",
                });
              } else {
                res.writeHead(400, { "Content-Type": "text/html" });
                res.end(
                  "<html><body><h2>Authentication Failed</h2><p>No token received.</p></body></html>"
                );
                server.close();
                resolve({ mode: "browser", serverId, success: false });
              }
            }
          });

          server.listen(port, () => {
            vscode.env.openExternal(vscode.Uri.parse(fullAuthUrl));
          });

          // Timeout after 5 minutes
          const timeout = setTimeout(() => {
            server.close();
            resolve({ mode: "browser", serverId, success: false });
          }, 5 * 60 * 1000);

          cancelToken.onCancellationRequested(() => {
            clearTimeout(timeout);
            server.close();
            resolve({ mode: "browser", serverId, success: false });
          });
        });
      }
    );

    if (result.success) {
      vscode.window.showInformationMessage(
        `Successfully authenticated with ${serverName}.`
      );
    } else {
      vscode.window.showWarningMessage(
        `Authentication with ${serverName} was cancelled or failed.`
      );
    }

    return result;
  }

  /**
   * Manual token/PAT/API key entry -- stored in SecretStorage.
   */
  private async _tokenAuth(
    serverId: string,
    serverName: string,
    authMode: string
  ): Promise<AuthResult> {
    const label = authMode === "api_key" ? "API Key" : "Token / PAT";

    const token = await vscode.window.showInputBox({
      title: `Authenticate: ${serverName}`,
      prompt: `Enter your ${label} for ${serverName}`,
      placeHolder: authMode === "api_key" ? "sk-..." : "ghp_... or pat-...",
      password: true,
      ignoreFocusOut: true,
    });

    if (!token) {
      return { mode: authMode, serverId, success: false };
    }

    await this._secretStore.setServerSecret(serverId, "auth_token", token);
    vscode.window.showInformationMessage(
      `${label} saved securely for ${serverName}.`
    );

    return {
      mode: authMode,
      serverId,
      success: true,
      tokenType: "auth_token",
    };
  }

  /**
   * OAuth2 client credentials flow.
   */
  private async _oauth2Auth(
    serverId: string,
    serverName: string,
    authUrl?: string
  ): Promise<AuthResult> {
    const clientId = await vscode.window.showInputBox({
      title: `OAuth2: ${serverName} (Step 1/2)`,
      prompt: "Enter OAuth2 Client ID",
      ignoreFocusOut: true,
    });
    if (!clientId) {
      return { mode: "oauth2", serverId, success: false };
    }

    const clientSecret = await vscode.window.showInputBox({
      title: `OAuth2: ${serverName} (Step 2/2)`,
      prompt: "Enter OAuth2 Client Secret",
      password: true,
      ignoreFocusOut: true,
    });
    if (!clientSecret) {
      return { mode: "oauth2", serverId, success: false };
    }

    await this._secretStore.setServerSecret(serverId, "oauth2_client_id", clientId);
    await this._secretStore.setServerSecret(serverId, "oauth2_client_secret", clientSecret);
    if (authUrl) {
      await this._secretStore.setServerSecret(serverId, "oauth2_token_url", authUrl);
    }

    vscode.window.showInformationMessage(
      `OAuth2 credentials saved securely for ${serverName}.`
    );

    return {
      mode: "oauth2",
      serverId,
      success: true,
      tokenType: "oauth2",
    };
  }

  private _findFreePort(): Promise<number> {
    return new Promise((resolve, reject) => {
      const server = http.createServer();
      server.listen(0, () => {
        const addr = server.address();
        if (addr && typeof addr === "object") {
          const port = addr.port;
          server.close(() => resolve(port));
        } else {
          reject(new Error("Could not allocate port"));
        }
      });
    });
  }
}
