import crypto from 'crypto';

const DRY_RUN = process.env.TWITTER_DRY_RUN === 'true';

const TWITTER_API_BASE = 'https://api.twitter.com';
const UPLOAD_API_BASE = 'https://upload.twitter.com';

const CONSUMER_KEY = process.env.TWITTER_CONSUMER_KEY || '';
const CONSUMER_SECRET = process.env.TWITTER_CONSUMER_SECRET || '';
const ACCESS_TOKEN = process.env.TWITTER_ACCESS_TOKEN || '';
const ACCESS_TOKEN_SECRET = process.env.TWITTER_ACCESS_TOKEN_SECRET || '';
const TWITTER_USER_ID = process.env.TWITTER_USER_ID || '';

// ── OAuth 1.0a Signing ──────────────────────────────────────

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

function generateNonce(): string {
  return crypto.randomBytes(16).toString('hex');
}

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, extraParams: Record<string, string> = {}): string {
  const oauthParams: Record<string, string> = {
    oauth_consumer_key: CONSUMER_KEY,
    oauth_nonce: generateNonce(),
    oauth_signature_method: 'HMAC-SHA1',
    oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
    oauth_token: ACCESS_TOKEN,
    oauth_version: '1.0',
  };

  const allParams = { ...oauthParams, ...extraParams };
  const signature = generateSignature(method, url, allParams, CONSUMER_SECRET, ACCESS_TOKEN_SECRET);
  oauthParams['oauth_signature'] = signature;

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

  return `OAuth ${headerParts}`;
}

// ── Tweet Posting ───────────────────────────────────────────

export async function postTweet(
  text: string,
  replyToId?: string
): Promise<{ tweetId: string; text: string }> {
  if (DRY_RUN) {
    console.log(`[twitter-api] DRY RUN — would post: "${text.substring(0, 80)}..."`);
    return { tweetId: `dry_${Date.now()}`, text };
  }

  const url = `${TWITTER_API_BASE}/2/tweets`;
  const body: Record<string, unknown> = { text };
  if (replyToId) {
    body.reply = { in_reply_to_tweet_id: replyToId };
  }

  const authHeader = buildAuthHeader('POST', url);
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: authHeader,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(30000),
  });

  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`Twitter POST /2/tweets failed ${response.status}: ${errText}`);
  }

  const data: any = await response.json();
  return { tweetId: data.data.id, text: data.data.text };
}

export async function postTweetWithMedia(
  text: string,
  mediaIds: string[],
  replyToId?: string
): Promise<{ tweetId: string; text: string }> {
  if (DRY_RUN) {
    console.log(`[twitter-api] DRY RUN — would post with media: "${text.substring(0, 80)}..." mediaIds=${mediaIds}`);
    return { tweetId: `dry_${Date.now()}`, text };
  }

  const url = `${TWITTER_API_BASE}/2/tweets`;
  const body: Record<string, unknown> = {
    text,
    media: { media_ids: mediaIds },
  };
  if (replyToId) {
    body.reply = { in_reply_to_tweet_id: replyToId };
  }

  const authHeader = buildAuthHeader('POST', url);
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: authHeader,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(30000),
  });

  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`Twitter POST /2/tweets (media) failed ${response.status}: ${errText}`);
  }

  const data: any = await response.json();
  return { tweetId: data.data.id, text: data.data.text };
}

// ── Media Upload (Simple — base64, for images <5MB) ────────

export async function uploadImageSimple(
  buffer: Buffer,
  mimeType: string
): Promise<string> {
  if (DRY_RUN) {
    console.log(`[twitter-api] DRY RUN — would upload image (${buffer.length} bytes, ${mimeType})`);
    return `dry_media_${Date.now()}`;
  }

  const url = `${UPLOAD_API_BASE}/1.1/media/upload.json`;
  const params: Record<string, string> = {
    media_data: buffer.toString('base64'),
    media_category: 'tweet_image',
  };

  const authHeader = buildAuthHeader('POST', url, params);

  const formBody = Object.entries(params)
    .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: formBody,
    signal: AbortSignal.timeout(60000),
  });

  if (!response.ok) {
    const errText = await response.text();
    throw new Error(`Twitter media upload (simple) failed ${response.status}: ${errText}`);
  }

  const data: any = await response.json();
  return data.media_id_string;
}

// ── Media Upload (Chunked — for videos and large images) ───

export async function uploadMediaChunked(
  buffer: Buffer,
  mimeType: string,
  category: 'tweet_image' | 'tweet_video' | 'tweet_gif' = 'tweet_video'
): Promise<string> {
  if (DRY_RUN) {
    console.log(`[twitter-api] DRY RUN — would upload chunked media (${buffer.length} bytes, ${mimeType})`);
    return `dry_media_${Date.now()}`;
  }

  const CHUNK_SIZE = 1024 * 1024; // 1MB

  // INIT
  const initUrl = `${UPLOAD_API_BASE}/1.1/media/upload.json`;
  const initParams: Record<string, string> = {
    command: 'INIT',
    total_bytes: buffer.length.toString(),
    media_type: mimeType,
    media_category: category,
  };

  const initAuth = buildAuthHeader('POST', initUrl, initParams);
  const initBody = Object.entries(initParams)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&');

  const initRes = await fetch(initUrl, {
    method: 'POST',
    headers: { Authorization: initAuth, 'Content-Type': 'application/x-www-form-urlencoded' },
    body: initBody,
    signal: AbortSignal.timeout(30000),
  });

  if (!initRes.ok) {
    const errText = await initRes.text();
    throw new Error(`Chunked upload INIT failed ${initRes.status}: ${errText}`);
  }

  const initData: any = await initRes.json();
  const mediaId = initData.media_id_string;

  // APPEND chunks (multipart/form-data with raw binary)
  let segmentIndex = 0;
  for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
    const chunk = buffer.subarray(offset, offset + CHUNK_SIZE);

    // For multipart/form-data, OAuth sig must NOT include body params
    const appendAuth = buildAuthHeader('POST', initUrl);

    const boundary = `----Boundary${crypto.randomBytes(8).toString('hex')}`;
    const bodyParts: Buffer[] = [];

    // command field
    bodyParts.push(Buffer.from(
      `--${boundary}\r\nContent-Disposition: form-data; name="command"\r\n\r\nAPPEND\r\n`
    ));
    // media_id field
    bodyParts.push(Buffer.from(
      `--${boundary}\r\nContent-Disposition: form-data; name="media_id"\r\n\r\n${mediaId}\r\n`
    ));
    // segment_index field
    bodyParts.push(Buffer.from(
      `--${boundary}\r\nContent-Disposition: form-data; name="segment_index"\r\n\r\n${segmentIndex}\r\n`
    ));
    // media field (raw binary — use "media" not "media_data" for multipart)
    bodyParts.push(Buffer.from(
      `--${boundary}\r\nContent-Disposition: form-data; name="media"; filename="media.mp4"\r\nContent-Type: ${mimeType}\r\n\r\n`
    ));
    bodyParts.push(chunk);
    bodyParts.push(Buffer.from(`\r\n--${boundary}--\r\n`));

    const appendBody = Buffer.concat(bodyParts);

    const appendRes = await fetch(initUrl, {
      method: 'POST',
      headers: {
        Authorization: appendAuth,
        'Content-Type': `multipart/form-data; boundary=${boundary}`,
      },
      body: appendBody,
      signal: AbortSignal.timeout(120000),
    });

    if (!appendRes.ok) {
      const errText = await appendRes.text();
      throw new Error(`Chunked upload APPEND segment ${segmentIndex} failed ${appendRes.status}: ${errText}`);
    }

    segmentIndex++;
  }

  // FINALIZE
  const finalParams: Record<string, string> = {
    command: 'FINALIZE',
    media_id: mediaId,
  };

  const finalAuth = buildAuthHeader('POST', initUrl, finalParams);
  const finalBody = Object.entries(finalParams)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&');

  const finalRes = await fetch(initUrl, {
    method: 'POST',
    headers: { Authorization: finalAuth, 'Content-Type': 'application/x-www-form-urlencoded' },
    body: finalBody,
    signal: AbortSignal.timeout(30000),
  });

  if (!finalRes.ok) {
    const errText = await finalRes.text();
    throw new Error(`Chunked upload FINALIZE failed ${finalRes.status}: ${errText}`);
  }

  const finalData: any = await finalRes.json();

  // Poll STATUS if processing
  if (finalData.processing_info) {
    await pollMediaStatus(mediaId, 300000); // 5 min timeout for video
  }

  return mediaId;
}

async function pollMediaStatus(mediaId: string, maxWaitMs: number): Promise<void> {
  const url = `${UPLOAD_API_BASE}/1.1/media/upload.json`;
  const start = Date.now();

  while (Date.now() - start < maxWaitMs) {
    const params: Record<string, string> = {
      command: 'STATUS',
      media_id: mediaId,
    };

    const authHeader = buildAuthHeader('GET', url, params);
    const queryString = Object.entries(params)
      .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
      .join('&');

    const res = await fetch(`${url}?${queryString}`, {
      headers: { Authorization: authHeader },
      signal: AbortSignal.timeout(15000),
    });

    if (!res.ok) {
      const errText = await res.text();
      throw new Error(`Media STATUS poll failed ${res.status}: ${errText}`);
    }

    const data: any = await res.json();
    const state = data.processing_info?.state;

    if (state === 'succeeded') return;
    if (state === 'failed') {
      throw new Error(`Media processing failed: ${JSON.stringify(data.processing_info.error)}`);
    }

    const waitSecs = data.processing_info?.check_after_secs || 5;
    await new Promise((r) => setTimeout(r, waitSecs * 1000));
  }

  throw new Error(`Media processing timed out after ${maxWaitMs}ms`);
}

// ── Search & Engagement ─────────────────────────────────────

export async function searchRecentTweets(
  query: string,
  maxResults: number = 25
): Promise<Array<{
  id: string;
  text: string;
  author_id: string;
  created_at: string;
  public_metrics: { like_count: number; retweet_count: number; reply_count: number };
}>> {
  if (DRY_RUN) {
    console.log(`[twitter-api] DRY RUN — would search: "${query}"`);
    return [];
  }

  const url = `${TWITTER_API_BASE}/2/tweets/search/recent`;
  const params = new URLSearchParams({
    query,
    max_results: maxResults.toString(),
    'tweet.fields': 'author_id,created_at,public_metrics',
  });

  const fullUrl = `${url}?${params}`;
  const authHeader = buildAuthHeader('GET', url, Object.fromEntries(params));

  const response = await fetch(fullUrl, {
    headers: { Authorization: authHeader },
    signal: AbortSignal.timeout(15000),
  });

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

  const data: any = await response.json();
  return data.data || [];
}

export async function likeTweet(tweetId: string): Promise<void> {
  if (DRY_RUN) {
    console.log(`[twitter-api] DRY RUN — would like tweet ${tweetId}`);
    return;
  }

  const url = `${TWITTER_API_BASE}/2/users/${TWITTER_USER_ID}/likes`;
  const authHeader = buildAuthHeader('POST', url);

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: authHeader,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ tweet_id: tweetId }),
    signal: AbortSignal.timeout(15000),
  });

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

export async function getMentions(
  sinceId?: string
): Promise<Array<{
  id: string;
  text: string;
  author_id: string;
  created_at: string;
}>> {
  if (DRY_RUN) {
    console.log('[twitter-api] DRY RUN — would fetch mentions');
    return [];
  }

  const url = `${TWITTER_API_BASE}/2/users/${TWITTER_USER_ID}/mentions`;
  const params = new URLSearchParams({
    max_results: '20',
    'tweet.fields': 'author_id,created_at',
  });
  if (sinceId) params.set('since_id', sinceId);

  const fullUrl = `${url}?${params}`;
  const authHeader = buildAuthHeader('GET', url, Object.fromEntries(params));

  const response = await fetch(fullUrl, {
    headers: { Authorization: authHeader },
    signal: AbortSignal.timeout(15000),
  });

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

  const data: any = await response.json();
  return data.data || [];
}

export function getUserId(): string {
  return TWITTER_USER_ID;
}
