/*
  Utilities to resolve TikTok/Instagram share links into a direct downloadable video URL.
  Tries, in order:
  1) Follow redirects
  2) Parse HTML meta tags (og:video) or JSON-LD contentUrl
  3) Use optional RapidAPI endpoints if configured in env
*/

export type ResolveResult = {
  sourcePlatform: 'tiktok' | 'instagram' | 'unknown';
  directUrl: string | null;
  debug?: Record<string, unknown>;
};

const DEFAULT_UA =
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36';

function detectPlatform(inputUrl: string): ResolveResult['sourcePlatform'] {
  const u = inputUrl.toLowerCase();
  if (u.includes('tiktok.com')) return 'tiktok';
  if (u.includes('instagram.com') || u.includes('instagr.am')) return 'instagram';
  return 'unknown';
}

async function fetchText(url: string): Promise<string | null> {
  try {
    const res = await fetch(url, {
      redirect: 'follow',
      headers: {
        'user-agent': DEFAULT_UA,
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      },
    });
    if (!res.ok) return null;
    return await res.text();
  } catch {
    return null;
  }
}

async function fetchInstagramHtml(url: string): Promise<string | null> {
  try {
    const cookieParts: string[] = [];
    if (process.env.INSTAGRAM_SESSIONID) cookieParts.push(`sessionid=${process.env.INSTAGRAM_SESSIONID}`);
    if (process.env.INSTAGRAM_CSRFTOKEN) cookieParts.push(`csrftoken=${process.env.INSTAGRAM_CSRFTOKEN}`);
    const cookie = cookieParts.join('; ');
    const res = await fetch(url, {
      redirect: 'follow',
      headers: {
        'user-agent': DEFAULT_UA,
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'x-ig-app-id': '936619743392459',
        'referer': 'https://www.instagram.com/',
        ...(cookie ? { cookie } : {}),
      },
    });
    if (!res.ok) return null;
    return await res.text();
  } catch {
    return null;
  }
}

async function fetchInstagramJson(shortcode: string): Promise<any | null> {
  const url = `https://www.instagram.com/p/${shortcode}/?__a=1&__d=dis`;
  try {
    const cookieParts: string[] = [];
    if (process.env.INSTAGRAM_SESSIONID) cookieParts.push(`sessionid=${process.env.INSTAGRAM_SESSIONID}`);
    if (process.env.INSTAGRAM_CSRFTOKEN) cookieParts.push(`csrftoken=${process.env.INSTAGRAM_CSRFTOKEN}`);
    const cookie = cookieParts.join('; ');
    const res = await fetch(url, {
      redirect: 'follow',
      headers: {
        'user-agent': DEFAULT_UA,
        'accept': 'application/json,text/*;q=0.8',
        'x-ig-app-id': '936619743392459',
        'referer': 'https://www.instagram.com/',
        ...(cookie ? { cookie } : {}),
      },
    });
    if (!res.ok) return null;
    const txt = await res.text();
    try {
      return JSON.parse(txt);
    } catch {
      // Sometimes returns HTML on auth wall
      return null;
    }
  } catch {
    return null;
  }
}

function extractOgVideo(html: string): string | null {
  // Try <meta property="og:video" content="...">
  const ogMatch = html.match(/<meta[^>]+property=["']og:video["'][^>]+content=["']([^"']+)["'][^>]*>/i);
  if (ogMatch && ogMatch[1]) return ogMatch[1];

  // Try JSON-LD contentUrl
  const ldBlocks = html.match(/<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi) || [];
  for (const block of ldBlocks) {
    const contentUrlMatch = block.match(new RegExp('"contentUrl"\\s*:\\s*"(https?:\\\/\\\/[^"\\\\]+)"', 'i'));
    if (contentUrlMatch && contentUrlMatch[1]) {
      return contentUrlMatch[1].replace(/\\\//g, '/');
    }
  }

  // Some pages include a direct mp4 in window.__INIT_PROPS__ or similar
  const mp4Match = html.match(/https?:\/\/[^"'\s]+\.mp4/);
  if (mp4Match) return mp4Match[0];

  return null;
}

function unescapeJsonUrl(s: string): string {
  try {
    return s.replace(/\\\//g, '/');
  } catch {
    return s;
  }
}

function findFirstMp4InObject(obj: unknown): string | null {
  const seen = new Set<unknown>();
  const stack: unknown[] = [obj];
  while (stack.length) {
    const cur = stack.pop();
    if (!cur || typeof cur !== 'object' || seen.has(cur)) continue;
    seen.add(cur);
    for (const [k, v] of Object.entries(cur as Record<string, unknown>)) {
      if (typeof v === 'string') {
        const val = unescapeJsonUrl(v);
        if (/^https?:\/\//i.test(val) && /\.mp4(\?|$)/i.test(val)) return val;
        // Prefer known TikTok fields
        if (/playaddr|downloadaddr|nowm/i.test(k) && /^https?:\/\//i.test(val)) return val;
      } else if (v && typeof v === 'object') {
        stack.push(v);
      }
    }
  }
  return null;
}

function extractJsonByScriptId(html: string, id: string): any | null {
  const byId = html.match(new RegExp(`<script[^>]+id=["']${id}["'][^>]*>([\\s\\S]*?)<\\/script>`, 'i'));
  if (byId && byId[1]) {
    try {
      return JSON.parse(byId[1]);
    } catch {
      // ignore
    }
  }
  return null;
}

function extractJsonByWindowAssign(html: string, name: string): any | null {
  // Find a <script> block that contains the marker name, then pull the first JSON object inside
  const block = html.match(new RegExp(`<script[^>]*>[\\s\\S]*?${name}[\\s\\S]*?<\\/script>`, 'i'));
  if (block && block[0]) {
    const jsonMatch = block[0].match(/\{[\s\S]*\}/);
    if (jsonMatch) {
      try {
        return JSON.parse(jsonMatch[0]);
      } catch {
        // ignore
      }
    }
  }
  return null;
}

function extractTikTokFromHtml(html: string): string | null {
  // 1) Try SIGI_STATE
  const sigi = extractJsonByScriptId(html, 'SIGI_STATE') || extractJsonByWindowAssign(html, 'SIGI_STATE');
  if (sigi) {
    // Common location: ItemModule -> <videoId> -> video.playAddr / downloadAddr
    const candidate = findFirstMp4InObject(sigi);
    if (candidate) return candidate;
  }
  // 2) Try __NEXT_DATA__
  const nextData = extractJsonByScriptId(html, '__NEXT_DATA__') || extractJsonByWindowAssign(html, '__NEXT_DATA__');
  if (nextData) {
    const candidate = findFirstMp4InObject(nextData);
    if (candidate) return candidate;
  }
  // 3) Fallback: any .mp4 URL in HTML
  const mp4Match = html.match(/https?:\/\/[^"'\s]+\.mp4[^"'\s]*/i);
  if (mp4Match) return unescapeJsonUrl(mp4Match[0]);
  return null;
}

function extractInstagramFromHtml(html: string): string | null {
  // 1) Any explicit video_url JSON field
  const videoUrlMatch = html.match(new RegExp('"video_url"\\s*:\\s*"(https:\\/[^\"]+?\\.mp4[^\"]*)"', 'i'));
  if (videoUrlMatch && videoUrlMatch[1]) return unescapeJsonUrl(videoUrlMatch[1]);
  // 2) playable_url
  const playableUrlMatch = html.match(new RegExp('"playable_url"\\s*:\\s*"(https:\\/[^\"]+?\\.mp4[^\"]*)"', 'i'));
  if (playableUrlMatch && playableUrlMatch[1]) return unescapeJsonUrl(playableUrlMatch[1]);
  // 3) Any .mp4 in JSON blocks
  const jsonBlocks = html.match(/<script[^>]*>([\s\S]*?)<\/script>/gi) || [];
  for (const block of jsonBlocks) {
    const mp4 = block.match(/https?:\\\/\\\/[^"'\s]+?\.mp4[^"'\s]*/i);
    if (mp4 && mp4[0]) return unescapeJsonUrl(mp4[0]);
  }
  // 4) Fallback handled by extractOgVideo
  return null;
}

function extractShortcodeFromInstagramUrl(shareUrl: string): string | null {
  const m = shareUrl.match(/instagram\.com\/(?:p|reel)\/([^\/?#]+)/i);
  return m ? m[1] : null;
}

async function tryRapidApi(platform: ResolveResult['sourcePlatform'], shareUrl: string): Promise<string | null> {
  const rapidApiKey = process.env.RAPIDAPI_KEY;
  if (!rapidApiKey) return null;

  try {
    if (platform === 'tiktok') {
      const apiUrl = process.env.RAPIDAPI_TIKTOK_API_URL; // e.g. https://tiktok-video-no-watermark2.p.rapidapi.com/
      const paramName = process.env.RAPIDAPI_TIKTOK_URL_PARAM || 'url';
      if (!apiUrl) return null;
      const url = new URL(apiUrl);
      url.searchParams.set(paramName, shareUrl);
      const resp = await fetch(url.toString(), {
        headers: {
          'X-RapidAPI-Key': rapidApiKey,
          'X-RapidAPI-Host': url.host,
        },
      });
      if (!resp.ok) return null;
      const data = await resp.json();
      // Heuristic extraction of a direct video URL from common RapidAPI responses
      const candidates: string[] = [];
      const pushIf = (v?: string) => v && candidates.push(v);
      pushIf(data?.data?.play); // some APIs
      pushIf(data?.data?.wmplay);
      pushIf(data?.data?.nowm); // nowatermark URL
      pushIf(data?.video); // generic
      pushIf(data?.result?.nowatermark);
      pushIf(data?.result?.video);
      const firstMp4 = candidates.find((c) => typeof c === 'string' && c.includes('.mp4')) || candidates[0];
      return firstMp4 || null;
    }

    if (platform === 'instagram') {
      const apiUrl = process.env.RAPIDAPI_INSTAGRAM_API_URL; // e.g. https://instagram-downloader-download-video.p.rapidapi.com/
      const paramName = process.env.RAPIDAPI_INSTAGRAM_URL_PARAM || 'url';
      if (!apiUrl) return null;
      const url = new URL(apiUrl);
      url.searchParams.set(paramName, shareUrl);
      const resp = await fetch(url.toString(), {
        headers: {
          'X-RapidAPI-Key': rapidApiKey,
          'X-RapidAPI-Host': url.host,
        },
      });
      if (!resp.ok) return null;
      const data = await resp.json();
      const candidates: string[] = [];
      const pushIf = (v?: string) => v && candidates.push(v);
      pushIf(data?.video);
      pushIf(data?.result?.video);
      pushIf(data?.data?.video);
      pushIf(data?.media);
      const firstMp4 = candidates.find((c) => typeof c === 'string' && c.includes('.mp4')) || candidates[0];
      return firstMp4 || null;
    }
  } catch {
    // ignore
  }
  return null;
}

export async function resolveDirectVideoUrl(shareUrl: string): Promise<ResolveResult> {
  const platform = detectPlatform(shareUrl);

  // 1) Attempt to follow redirects to already-direct mp4
  try {
    const res = await fetch(shareUrl, {
      redirect: 'follow',
      headers: { 'user-agent': DEFAULT_UA },
    });
    if (res.ok) {
      const finalUrl = res.url;
      if (finalUrl && /\.mp4(\?|$)/i.test(finalUrl)) {
        return { sourcePlatform: platform, directUrl: finalUrl };
      }
    }
  } catch {
    // continue
  }

  // 2) Parse HTML and try platform-specific extraction
  const html = await fetchText(shareUrl);
  if (html) {
    // Platform-first scrapers
    if (platform === 'tiktok') {
      const tt = extractTikTokFromHtml(html) || extractOgVideo(html);
      if (tt) return { sourcePlatform: platform, directUrl: tt };
    }
    if (platform === 'instagram') {
      // Try JSON endpoint with optional session cookie
      const code = extractShortcodeFromInstagramUrl(shareUrl);
      if (code) {
        const json = await fetchInstagramJson(code);
        if (json) {
          const mp4FromJson = findFirstMp4InObject(json);
          if (mp4FromJson) return { sourcePlatform: platform, directUrl: mp4FromJson };
        }
      }

      // Try HTML again but with optional session cookie
      const igHtml = await fetchInstagramHtml(shareUrl);
      if (igHtml) {
        const ig = extractInstagramFromHtml(igHtml) || extractOgVideo(igHtml);
        if (ig) return { sourcePlatform: platform, directUrl: ig };
      } else {
        const ig = extractInstagramFromHtml(html) || extractOgVideo(html);
        if (ig) return { sourcePlatform: platform, directUrl: ig };
      }
    }
    // As a generic fallback, og:video
    const generic = extractOgVideo(html);
    if (generic) return { sourcePlatform: platform, directUrl: generic };
  }

  // 3) Try RapidAPI fallback if configured
  const rapid = await tryRapidApi(platform, shareUrl);
  if (rapid) {
    return { sourcePlatform: platform, directUrl: rapid };
  }

  return { sourcePlatform: platform, directUrl: null };
}


