import { Pool } from "pg";
import { TokenEncryption } from "./encryption";
import {
  OAuthError,
  OAuthErrorType
} from "./types";
const TOKEN_ENDPOINTS = {
  facebook: "https://graph.facebook.com/v18.0/oauth/access_token",
  instagram: "https://graph.facebook.com/v18.0/oauth/access_token",
  // Uses Facebook
  google: "https://oauth2.googleapis.com/token"
};
const REFRESH_CONFIG = {
  // Refresh tokens when they expire within this window
  expirationWindowMinutes: 10,
  // Retry failed refreshes
  maxRetries: 3,
  retryDelayMs: 1e3
};
const databaseUrl = process.env.DATABASE_URL;
const connectionString = databaseUrl.includes("?") ? `${databaseUrl}&pgbouncer=true` : `${databaseUrl}?pgbouncer=true`;
const pool = new Pool({
  connectionString,
  ssl: { rejectUnauthorized: false },
  connectionTimeoutMillis: 1e4,
  idleTimeoutMillis: 3e4,
  max: 20
});
class TokenRefreshService {
  /**
   * Check if a token needs refresh based on expiration
   *
   * @param expiresAt - Token expiration date
   * @returns true if token needs refresh
   */
  static needsRefresh(expiresAt) {
    if (!expiresAt) {
      return false;
    }
    const now = /* @__PURE__ */ new Date();
    const expirationWindow = new Date(
      now.getTime() + REFRESH_CONFIG.expirationWindowMinutes * 60 * 1e3
    );
    return expiresAt <= expirationWindow;
  }
  /**
   * Refresh an OAuth token
   *
   * @param provider - OAuth provider
   * @param refreshToken - Current refresh token
   * @param clientId - OAuth client ID
   * @param clientSecret - OAuth client secret
   * @returns Refresh result with new tokens
   */
  static async refreshToken(provider, refreshToken, clientId, clientSecret) {
    try {
      const endpoint = TOKEN_ENDPOINTS[provider];
      const params = this.buildRefreshParams(provider, refreshToken, clientId, clientSecret);
      const response = await fetch(endpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body: new URLSearchParams(params)
      });
      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(`Token refresh failed: ${errorData.error || response.statusText}`);
      }
      const data = await response.json();
      return {
        success: true,
        accessToken: data.access_token,
        refreshToken: data.refresh_token || refreshToken,
        // Some providers don't return new refresh token
        expiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : "Unknown error"
      };
    }
  }
  /**
   * Build provider-specific refresh parameters
   */
  static buildRefreshParams(provider, refreshToken, clientId, clientSecret) {
    const baseParams = {
      grant_type: "refresh_token",
      refresh_token: refreshToken,
      client_id: clientId,
      client_secret: clientSecret
    };
    switch (provider) {
      case "facebook":
      case "instagram":
        return baseParams;
      case "google":
        return baseParams;
      default:
        return baseParams;
    }
  }
  /**
   * Refresh expired tokens for a specific account
   *
   * @param accountId - Account ID from Better Auth account table
   * @returns true if refresh succeeded
   */
  static async refreshAccountTokens(accountId) {
    const client = await pool.connect();
    try {
      const accountResult = await client.query(
        `SELECT
          id,
          "providerId",
          "userId",
          "encryptedAccessToken",
          "encryptedRefreshToken",
          "encryptionKeyId",
          "accessTokenExpiresAt",
          "connectionStatus"
        FROM account
        WHERE id = $1`,
        [accountId]
      );
      if (accountResult.rows.length === 0) {
        console.error(`Account not found: ${accountId}`);
        return false;
      }
      const account = accountResult.rows[0];
      const provider = account.providerId;
      if (!this.needsRefresh(account.accessTokenExpiresAt)) {
        console.log(`Token for account ${accountId} does not need refresh`);
        return true;
      }
      if (!account.encryptedRefreshToken) {
        console.error(`No refresh token available for account ${accountId}`);
        await this.markAccountStatus(accountId, "expired");
        return false;
      }
      const encryptedRefreshToken = JSON.parse(
        account.encryptedRefreshToken
      );
      const refreshToken = await TokenEncryption.decrypt(
        encryptedRefreshToken.encrypted,
        encryptedRefreshToken.iv,
        encryptedRefreshToken.keyId
      );
      const clientId = this.getClientId(provider);
      const clientSecret = this.getClientSecret(provider);
      if (!clientId || !clientSecret) {
        throw new OAuthError(
          OAuthErrorType.PROVIDER_ERROR,
          `Missing OAuth credentials for ${provider}`,
          provider
        );
      }
      const refreshResult = await this.refreshToken(
        provider,
        refreshToken,
        clientId,
        clientSecret
      );
      if (!refreshResult.success) {
        console.error(`Token refresh failed for account ${accountId}: ${refreshResult.error}`);
        await this.markAccountStatus(accountId, "expired");
        return false;
      }
      const { encryptedAccessToken, encryptedRefreshToken: newEncryptedRefreshToken } = await TokenEncryption.encryptTokenPair(
        refreshResult.accessToken,
        refreshResult.refreshToken
      );
      await client.query(
        `UPDATE account
        SET
          "encryptedAccessToken" = $1,
          "encryptedRefreshToken" = $2,
          "encryptionKeyId" = $3,
          "accessTokenExpiresAt" = $4,
          "connectionStatus" = 'active',
          "updatedAt" = NOW()
        WHERE id = $5`,
        [
          JSON.stringify(encryptedAccessToken),
          newEncryptedRefreshToken ? JSON.stringify(newEncryptedRefreshToken) : null,
          encryptedAccessToken.keyId,
          refreshResult.expiresAt || null,
          accountId
        ]
      );
      console.log(`Successfully refreshed tokens for account ${accountId}`);
      return true;
    } catch (error) {
      console.error(`Error refreshing tokens for account ${accountId}:`, error);
      await this.markAccountStatus(accountId, "error");
      return false;
    } finally {
      client.release();
    }
  }
  /**
   * Refresh expired tokens for all accounts of a user
   *
   * @param userId - User ID
   * @returns Number of accounts successfully refreshed
   */
  static async refreshUserTokens(userId) {
    const client = await pool.connect();
    try {
      const accountsResult = await client.query(
        `SELECT id, "accessTokenExpiresAt"
        FROM account
        WHERE "userId" = $1
        AND "encryptedRefreshToken" IS NOT NULL
        AND "connectionStatus" IN ('active', 'expired')`,
        [userId]
      );
      let refreshedCount = 0;
      for (const account of accountsResult.rows) {
        if (this.needsRefresh(account.accessTokenExpiresAt)) {
          const success = await this.refreshAccountTokens(account.id);
          if (success) {
            refreshedCount++;
          }
        }
      }
      return refreshedCount;
    } finally {
      client.release();
    }
  }
  /**
   * Refresh all expired tokens in the system (for cron job)
   *
   * @returns Number of accounts successfully refreshed
   */
  static async refreshAllExpiredTokens() {
    const client = await pool.connect();
    try {
      const expirationWindow = new Date(
        Date.now() + REFRESH_CONFIG.expirationWindowMinutes * 60 * 1e3
      );
      const accountsResult = await client.query(
        `SELECT id
        FROM account
        WHERE "encryptedRefreshToken" IS NOT NULL
        AND "accessTokenExpiresAt" <= $1
        AND "connectionStatus" IN ('active', 'expired')`,
        [expirationWindow]
      );
      let refreshedCount = 0;
      for (const account of accountsResult.rows) {
        const success = await this.refreshAccountTokens(account.id);
        if (success) {
          refreshedCount++;
        }
        await new Promise((resolve) => setTimeout(resolve, REFRESH_CONFIG.retryDelayMs));
      }
      console.log(`Refreshed ${refreshedCount} accounts out of ${accountsResult.rows.length}`);
      return refreshedCount;
    } finally {
      client.release();
    }
  }
  /**
   * Mark account connection status
   */
  static async markAccountStatus(accountId, status) {
    const client = await pool.connect();
    try {
      await client.query(
        `UPDATE account SET "connectionStatus" = $1, "updatedAt" = NOW() WHERE id = $2`,
        [status, accountId]
      );
    } finally {
      client.release();
    }
  }
  /**
   * Get OAuth client ID from environment
   */
  static getClientId(provider) {
    switch (provider) {
      case "facebook":
        return process.env.FACEBOOK_CLIENT_ID;
      case "instagram":
        return process.env.FACEBOOK_CLIENT_ID;
      // Instagram uses Facebook app
      case "google":
        return process.env.GOOGLE_CLIENT_ID;
      default:
        return void 0;
    }
  }
  /**
   * Get OAuth client secret from environment
   */
  static getClientSecret(provider) {
    switch (provider) {
      case "facebook":
        return process.env.FACEBOOK_CLIENT_SECRET;
      case "instagram":
        return process.env.FACEBOOK_CLIENT_SECRET;
      // Instagram uses Facebook app
      case "google":
        return process.env.GOOGLE_CLIENT_SECRET;
      default:
        return void 0;
    }
  }
  /**
   * Get decrypted access token for an account (refreshes if needed)
   *
   * @param accountId - Account ID
   * @returns Decrypted access token
   */
  static async getAccessToken(accountId) {
    const needsRefresh = await this.checkAndRefreshIfNeeded(accountId);
    if (!needsRefresh) {
      throw new OAuthError(
        OAuthErrorType.TOKEN_EXPIRED,
        "Token refresh failed",
        void 0
      );
    }
    const client = await pool.connect();
    try {
      const result = await client.query(
        `SELECT "encryptedAccessToken", "encryptionKeyId" FROM account WHERE id = $1`,
        [accountId]
      );
      if (result.rows.length === 0) {
        throw new Error(`Account not found: ${accountId}`);
      }
      const encryptedToken = JSON.parse(result.rows[0].encryptedAccessToken);
      return await TokenEncryption.decrypt(
        encryptedToken.encrypted,
        encryptedToken.iv,
        encryptedToken.keyId
      );
    } finally {
      client.release();
    }
  }
  /**
   * Check if account needs refresh and refresh if necessary
   */
  static async checkAndRefreshIfNeeded(accountId) {
    const client = await pool.connect();
    try {
      const result = await client.query(
        `SELECT "accessTokenExpiresAt" FROM account WHERE id = $1`,
        [accountId]
      );
      if (result.rows.length === 0) {
        return false;
      }
      const expiresAt = result.rows[0].accessTokenExpiresAt;
      if (this.needsRefresh(expiresAt)) {
        return await this.refreshAccountTokens(accountId);
      }
      return true;
    } finally {
      client.release();
    }
  }
}
export {
  TokenRefreshService
};
