import type { Customer } from '../billing/middleware.js';
import type { OAuthCredentials } from '../multitenancy/credential-store.js';

const TWITTER_API_BASE = 'https://api.twitter.com/2';
const TWITTER_UPLOAD_BASE = 'https://upload.twitter.com/1.1';

function getEnvToken(account: string): string {
  const envKey = `TWITTER_${account.toUpperCase()}_BEARER_TOKEN`;
  return process.env[envKey] ?? '';
}

async function resolveToken(args: { account?: string }, customer?: Customer): Promise<string> {
  if (customer) {
    const creds = await customer.getCredential<OAuthCredentials>('twitter');
    if (!creds) throw new Error('Twitter not connected for this account');
    return creds.accessToken;
  }
  const token = getEnvToken(args.account ?? 'default');
  if (!token) throw new Error('Missing Twitter credentials. Set TWITTER_{ACCOUNT}_BEARER_TOKEN');
  return token;
}

async function twitterRequest(
  endpoint: string,
  bearerToken: string,
  params?: Record<string, string>
) {
  const url = new URL(`${TWITTER_API_BASE}${endpoint}`);
  if (params) {
    Object.entries(params).forEach(([key, value]) => {
      if (value) url.searchParams.append(key, value);
    });
  }

  const res = await fetch(url.toString(), {
    headers: { 'Authorization': `Bearer ${bearerToken}` },
    signal: AbortSignal.timeout(15000),
  });

  if (!res.ok) {
    const error = await res.text();
    throw new Error(`Twitter API error (${res.status}): ${error}`);
  }

  return res.json();
}

export async function searchTweets(
  args: { query: string; max_results?: number; account?: string },
  customer?: Customer
): Promise<Array<{ id: string; text: string; author_id?: string; created_at?: string }>> {
  const bearerToken = await resolveToken(args, customer);

  const data = await twitterRequest('/tweets/search/recent', bearerToken, {
    query: args.query,
    max_results: String(Math.min(args.max_results ?? 10, 100)),
    'tweet.fields': 'created_at,author_id',
  });

  return (data.data ?? []) as Array<{ id: string; text: string; author_id?: string; created_at?: string }>;
}

export async function getUserProfile(
  args: { username: string; account?: string },
  customer?: Customer
): Promise<{
  id: string;
  name: string;
  username: string;
  description?: string;
  followers_count?: number;
  following_count?: number;
  tweet_count?: number;
}> {
  const bearerToken = await resolveToken(args, customer);

  const data = await twitterRequest(`/users/by/username/${args.username}`, bearerToken, {
    'user.fields': 'description,public_metrics',
  });

  const user = data.data as { id: string; name: string; username: string; description?: string; public_metrics?: { followers_count?: number; following_count?: number; tweet_count?: number } };

  return {
    id: user.id,
    name: user.name,
    username: user.username,
    description: user.description,
    followers_count: user.public_metrics?.followers_count,
    following_count: user.public_metrics?.following_count,
    tweet_count: user.public_metrics?.tweet_count,
  };
}

export async function getUserTweets(
  args: { username: string; max_results?: number; account?: string },
  customer?: Customer
): Promise<Array<{ id: string; text: string; created_at?: string }>> {
  const bearerToken = await resolveToken(args, customer);

  const userData = await twitterRequest(`/users/by/username/${args.username}`, bearerToken);
  const userId = userData.data?.id;
  if (!userId) throw new Error(`User @${args.username} not found`);

  const data = await twitterRequest(`/users/${userId}/tweets`, bearerToken, {
    max_results: String(Math.min(args.max_results ?? 10, 100)),
    'tweet.fields': 'created_at',
  });

  return (data.data ?? []) as Array<{ id: string; text: string; created_at?: string }>;
}

export async function createTweet(
  args: { text: string; account?: string },
  _customer?: Customer
): Promise<{ message: string }> {
  throw new Error(
    'Twitter/X posting requires a paid API tier. ' +
    'The free tier is read-only (500 tweets/month). ' +
    'Upgrade to Basic ($100/month) or Pro ($5,000/month) at https://developer.twitter.com/en/portal/products'
  );
}

export async function uploadVideoAndTweet(
  args: { videoUrl: string; text: string; account?: string },
  customer?: Customer
): Promise<{ success: boolean; tweet_id: string; url: string }> {
  const bearerToken = await resolveToken(args, customer);

  // 1. Download video
  const videoRes = await fetch(args.videoUrl, { signal: AbortSignal.timeout(60000) });
  if (!videoRes.ok) {
    throw new Error(`Failed to download video: ${videoRes.status} ${videoRes.statusText}`);
  }
  const videoBuffer = Buffer.from(await videoRes.arrayBuffer());
  const totalBytes = videoBuffer.length;

  // 2. INIT
  const initUrl = new URL(`${TWITTER_UPLOAD_BASE}/media/upload.json`);
  initUrl.searchParams.set('command', 'INIT');
  initUrl.searchParams.set('total_bytes', String(totalBytes));
  initUrl.searchParams.set('media_type', 'video/mp4');

  const initRes = await fetch(initUrl.toString(), {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${bearerToken}` },
    signal: AbortSignal.timeout(15000),
  });

  if (!initRes.ok) {
    const err = await initRes.text();
    throw new Error(`Twitter media INIT error (${initRes.status}): ${err}`);
  }

  const initData = (await initRes.json()) as { media_id_string?: string };
  const mediaId = initData.media_id_string;
  if (!mediaId) throw new Error('Twitter media INIT did not return media_id_string');

  // 3. APPEND chunks
  const CHUNK_SIZE = 2 * 1024 * 1024;
  let segmentIndex = 0;
  for (let offset = 0; offset < totalBytes; offset += CHUNK_SIZE) {
    const chunk = videoBuffer.subarray(offset, offset + CHUNK_SIZE);

    const formData = new FormData();
    formData.append('media', new Blob([chunk]));

    const appendUrl = new URL(`${TWITTER_UPLOAD_BASE}/media/upload.json`);
    appendUrl.searchParams.set('command', 'APPEND');
    appendUrl.searchParams.set('media_id', mediaId);
    appendUrl.searchParams.set('segment_index', String(segmentIndex));

    const appendRes = await fetch(appendUrl.toString(), {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${bearerToken}` },
      body: formData,
      signal: AbortSignal.timeout(30000),
    });

    if (!appendRes.ok) {
      const err = await appendRes.text();
      throw new Error(`Twitter media APPEND error (${appendRes.status}): ${err}`);
    }

    segmentIndex++;
  }

  // 4. FINALIZE
  const finalizeUrl = new URL(`${TWITTER_UPLOAD_BASE}/media/upload.json`);
  finalizeUrl.searchParams.set('command', 'FINALIZE');
  finalizeUrl.searchParams.set('media_id', mediaId);

  const finalizeRes = await fetch(finalizeUrl.toString(), {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${bearerToken}` },
    signal: AbortSignal.timeout(15000),
  });

  if (!finalizeRes.ok) {
    const err = await finalizeRes.text();
    throw new Error(`Twitter media FINALIZE error (${finalizeRes.status}): ${err}`);
  }

  const finalizeData = (await finalizeRes.json()) as {
    processing_info?: { state?: string; check_after_secs?: number; error?: { message?: string } };
  };

  // 5. STATUS polling
  let processingInfo = finalizeData.processing_info;
  while (processingInfo && processingInfo.state !== 'succeeded') {
    if (processingInfo.state === 'failed') {
      const errMsg = processingInfo.error?.message ?? 'Unknown processing error';
      throw new Error(`Twitter media processing failed: ${errMsg}`);
    }

    const waitSeconds = processingInfo.check_after_secs ?? 5;
    await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));

    const statusUrl = new URL(`${TWITTER_UPLOAD_BASE}/media/upload.json`);
    statusUrl.searchParams.set('command', 'STATUS');
    statusUrl.searchParams.set('media_id', mediaId);

    const statusRes = await fetch(statusUrl.toString(), {
      method: 'GET',
      headers: { 'Authorization': `Bearer ${bearerToken}` },
      signal: AbortSignal.timeout(15000),
    });

    if (!statusRes.ok) {
      const err = await statusRes.text();
      throw new Error(`Twitter media STATUS error (${statusRes.status}): ${err}`);
    }

    const statusData = (await statusRes.json()) as {
      processing_info?: { state?: string; check_after_secs?: number; error?: { message?: string } };
    };
    processingInfo = statusData.processing_info;

    if (processingInfo?.state === 'failed') {
      const errMsg = processingInfo.error?.message ?? 'Unknown processing error';
      throw new Error(`Twitter media processing failed: ${errMsg}`);
    }
  }

  // 6. Post tweet with video
  const tweetRes = await fetch(`${TWITTER_API_BASE}/tweets`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${bearerToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      text: args.text,
      media: { media_keys: [mediaId] },
    }),
    signal: AbortSignal.timeout(15000),
  });

  if (!tweetRes.ok) {
    const err = await tweetRes.text();
    throw new Error(`Twitter tweet error (${tweetRes.status}): ${err}`);
  }

  const tweetData = (await tweetRes.json()) as { data?: { id?: string } };
  const tweetId = tweetData.data?.id;
  if (!tweetId) throw new Error('Twitter tweet creation did not return an ID');

  return {
    success: true,
    tweet_id: tweetId,
    url: `https://twitter.com/i/web/status/${tweetId}`,
  };
}
