#!/usr/bin/env npx tsx
/**
 * Twitter OAuth 1.0a PIN-based token generator.
 * Run: npx tsx backend/src/twitter/generate-oauth-tokens.ts
 *
 * 1. Opens an authorization URL in the console
 * 2. You visit it in your browser and authorize
 * 3. Twitter gives you a PIN
 * 4. Enter the PIN here
 * 5. Outputs TWITTER_ACCESS_TOKEN and TWITTER_ACCESS_TOKEN_SECRET
 */

import crypto from 'crypto';
import readline from 'readline';

const CONSUMER_KEY = process.env.TWITTER_CONSUMER_KEY || 'NxcpTUizgnrl9OmGPazoehxSd';
const CONSUMER_SECRET = process.env.TWITTER_CONSUMER_SECRET || 'UF8F8EWwekoqNPVceIzLXgp6WbHT8gqs8IDAB0oE1cQ3fYSZVE';

function percentEncode(str: string): string {
  return encodeURIComponent(str)
    .replace(/!/g, '%21')
    .replace(/\*/g, '%2A')
    .replace(/'/g, '%27')
    .replace(/\(/g, '%28')
    .replace(/\)/g, '%29');
}

function generateSignature(
  method: string,
  url: string,
  params: Record<string, string>,
  consumerSecret: string,
  tokenSecret: string
): string {
  const sortedKeys = Object.keys(params).sort();
  const paramString = sortedKeys.map((k) => `${percentEncode(k)}=${percentEncode(params[k])}`).join('&');
  const baseString = `${method.toUpperCase()}&${percentEncode(url)}&${percentEncode(paramString)}`;
  const signingKey = `${percentEncode(consumerSecret)}&${percentEncode(tokenSecret)}`;
  return crypto.createHmac('sha1', signingKey).update(baseString).digest('base64');
}

function buildAuthHeader(method: string, url: string, oauthToken: string, tokenSecret: string, extraParams: Record<string, string> = {}): string {
  const oauthParams: Record<string, string> = {
    oauth_consumer_key: CONSUMER_KEY,
    oauth_nonce: crypto.randomBytes(16).toString('hex'),
    oauth_signature_method: 'HMAC-SHA1',
    oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
    oauth_version: '1.0',
  };
  if (oauthToken) oauthParams.oauth_token = oauthToken;

  const allParams = { ...oauthParams, ...extraParams };
  const signature = generateSignature(method, url, allParams, CONSUMER_SECRET, tokenSecret);
  oauthParams.oauth_signature = signature;

  const headerParts = Object.keys(oauthParams)
    .sort()
    .map((k) => `${percentEncode(k)}="${percentEncode(oauthParams[k])}"`)
    .join(', ');

  return `OAuth ${headerParts}`;
}

async function step1_requestToken(): Promise<{ oauthToken: string; oauthTokenSecret: string }> {
  const url = 'https://api.twitter.com/oauth/request_token';
  const extraParams = { oauth_callback: 'oob' };
  const authHeader = buildAuthHeader('POST', url, '', '', extraParams);

  const body = Object.entries(extraParams)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&');

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: authHeader,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body,
  });

  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`Request token failed ${response.status}: ${errText}`);
  }

  const text = await response.text();
  const params = new URLSearchParams(text);
  return {
    oauthToken: params.get('oauth_token') || '',
    oauthTokenSecret: params.get('oauth_token_secret') || '',
  };
}

async function step3_accessToken(oauthToken: string, oauthTokenSecret: string, pin: string): Promise<{
  accessToken: string;
  accessTokenSecret: string;
  userId: string;
  screenName: string;
}> {
  const url = 'https://api.twitter.com/oauth/access_token';
  const extraParams = { oauth_verifier: pin };
  const authHeader = buildAuthHeader('POST', url, oauthToken, oauthTokenSecret, extraParams);

  const body = Object.entries(extraParams)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&');

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: authHeader,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body,
  });

  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`Access token failed ${response.status}: ${errText}`);
  }

  const text = await response.text();
  const params = new URLSearchParams(text);
  return {
    accessToken: params.get('oauth_token') || '',
    accessTokenSecret: params.get('oauth_token_secret') || '',
    userId: params.get('user_id') || '',
    screenName: params.get('screen_name') || '',
  };
}

function prompt(question: string): Promise<string> {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  return new Promise((resolve) => {
    rl.question(question, (answer) => {
      rl.close();
      resolve(answer.trim());
    });
  });
}

async function main() {
  console.log('=== Twitter OAuth 1.0a Token Generator ===\n');
  console.log(`Consumer Key: ${CONSUMER_KEY.slice(0, 8)}...`);
  console.log('');

  // Step 1: Get request token
  console.log('Step 1: Requesting temporary token...');
  const { oauthToken, oauthTokenSecret } = await step1_requestToken();
  console.log('Got request token.\n');

  // Step 2: User authorizes
  const authorizeUrl = `https://api.twitter.com/oauth/authorize?oauth_token=${oauthToken}`;
  console.log('Step 2: Open this URL in your browser and authorize the app:\n');
  console.log(`  ${authorizeUrl}\n`);

  const pin = await prompt('Step 3: Enter the PIN from Twitter: ');

  if (!pin) {
    console.error('No PIN entered. Aborting.');
    process.exit(1);
  }

  // Step 3: Exchange PIN for access token
  console.log('\nStep 4: Exchanging PIN for access tokens...');
  const result = await step3_accessToken(oauthToken, oauthTokenSecret, pin);

  console.log('\n=== SUCCESS ===\n');
  console.log(`Screen Name: @${result.screenName}`);
  console.log(`User ID: ${result.userId}`);
  console.log('');
  console.log('Add these to your backend/.env:');
  console.log('');
  console.log(`TWITTER_ACCESS_TOKEN=${result.accessToken}`);
  console.log(`TWITTER_ACCESS_TOKEN_SECRET=${result.accessTokenSecret}`);
  console.log(`TWITTER_USER_ID=${result.userId}`);
}

main().catch((err) => {
  console.error('Error:', err.message);
  process.exit(1);
});
