import "server-only";

import { promises as fs } from "node:fs";
import path from "node:path";

import { getMediaProcessingConfig } from "@/lib/server/media-config";
import { MediaProcessingError } from "@/lib/server/media-errors";
import { getYtDlpAuthOptions } from "@/lib/social-download/yt-dlp-auth";
import { createYtDlpClient } from "@/lib/social-download/yt-dlp-client";
import { assertPathWithinRoot, createTempOutputPath } from "@/lib/uploads";
import { sanitizeBaseName } from "@/lib/validations";
import type { TwitterDownloadResult, TwitterVideoInfo } from "@/types/media-processing";

interface YoutubeDlPayload {
  title?: string;
  webpage_url?: string;
  uploader?: string;
  uploader_id?: string | null;
  uploader_url?: string | null;
  duration?: number | string | null;
  width?: number | string | null;
  height?: number | string | null;
  thumbnail?: string | null;
  extractor_key?: string | null;
  vcodec?: string | null;
  entries?: YoutubeDlPayload[];
  _type?: string;
  id?: string;
  formats?: YoutubeDlFormat[];
}

interface YoutubeDlFormat {
  format_id?: string;
  ext?: string;
  vcodec?: string;
  acodec?: string;
  format_note?: string;
  width?: number;
  height?: number;
  tbr?: number;
  filesize?: number;
  filesize_approx?: number;
  protocol?: string;
}

interface YoutubeDlError extends Error {
  stderr?: string | Buffer | null;
  stdout?: string | Buffer | null;
  shortMessage?: string | null;
  code?: string | null;
  path?: string | null;
  errno?: string | number | null;
}

interface FxTwitterApiResponse {
  tweet?: {
    author?: {
      avatar_url?: string | null;
    } | null;
  } | null;
}

interface VxTwitterApiResponse {
  user_profile_image_url?: string | null;
}

function getYoutubeDlClient() {
  return createYtDlpClient();
}

function pickVideoEntry(payload: YoutubeDlPayload) {
  const entries = Array.isArray(payload.entries) ? payload.entries : [];
  if (entries.length === 0) {
    return payload;
  }

  const firstVideoEntry =
    entries.find((entry) => {
      const videoCodec = typeof entry.vcodec === "string" ? entry.vcodec : "";
      return videoCodec !== "" && videoCodec !== "none";
    }) || entries[0];

  return firstVideoEntry ?? payload;
}

function toNonEmptyString(value: unknown) {
  if (typeof value !== "string") {
    return null;
  }

  const trimmedValue = value.trim();
  return trimmedValue ? trimmedValue : null;
}

function toNonNegativeNumber(value: unknown) {
  if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
    return value;
  }

  if (typeof value === "string") {
    const parsedValue = Number(value);
    if (Number.isFinite(parsedValue) && parsedValue >= 0) {
      return parsedValue;
    }
  }

  return null;
}

function toTwitterVideoInfo(url: string, payload: YoutubeDlPayload): TwitterVideoInfo {
  const selectedEntry = pickVideoEntry(payload);

  const title = toNonEmptyString(selectedEntry.title) || "Twitter/X Video";
  const webpageUrl = toNonEmptyString(selectedEntry.webpage_url) || url;
  const uploaderHandle = toNonEmptyString(selectedEntry.uploader_id);
  const uploaderProfileUrl =
    toNonEmptyString(selectedEntry.uploader_url) ||
    (uploaderHandle ? `https://x.com/${encodeURIComponent(uploaderHandle)}` : null);

  return {
    sourceUrl: url,
    webpageUrl,
    title,
    uploader: toNonEmptyString(selectedEntry.uploader),
    uploaderHandle,
    uploaderProfileUrl,
    uploaderProfileImageUrl: null,
    durationSeconds: toNonNegativeNumber(selectedEntry.duration),
    width: toNonNegativeNumber(selectedEntry.width),
    height: toNonNegativeNumber(selectedEntry.height),
    thumbnailUrl: toNonEmptyString(selectedEntry.thumbnail),
    extractorKey: toNonEmptyString(selectedEntry.extractor_key)
  };
}

function toSafeHttpUrl(value: unknown) {
  const normalizedValue = toNonEmptyString(value);
  if (!normalizedValue) {
    return null;
  }

  try {
    const parsedUrl = new URL(normalizedValue);
    if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
      return null;
    }

    return parsedUrl.toString();
  } catch {
    return null;
  }
}

function normalizeTwitterProfileImageUrl(url: string | null) {
  if (!url) {
    return null;
  }

  return url.includes("_normal.") ? url.replace("_normal.", "_200x200.") : url;
}

function extractTweetIdFromUrl(rawUrl: string | null) {
  if (!rawUrl) {
    return null;
  }

  try {
    const parsedUrl = new URL(rawUrl);
    const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
    const statusSegmentIndex = pathSegments.findIndex((segment) => segment.toLowerCase() === "status");

    if (statusSegmentIndex < 0 || !pathSegments[statusSegmentIndex + 1]) {
      return null;
    }

    const candidateTweetId = pathSegments[statusSegmentIndex + 1].trim();
    return /^\d+$/.test(candidateTweetId) ? candidateTweetId : null;
  } catch {
    return null;
  }
}

function getTweetIdFromPayload(sourceUrl: string, payload: YoutubeDlPayload) {
  const selectedEntry = pickVideoEntry(payload);

  return (
    extractTweetIdFromUrl(toNonEmptyString(selectedEntry.webpage_url)) ||
    extractTweetIdFromUrl(sourceUrl) ||
    toNonEmptyString(selectedEntry.id)
  );
}

const TWITTER_PROFILE_LOOKUP_HEADERS = {
  "user-agent": "Mozilla/5.0",
  accept: "application/json"
} as const;

async function fetchTwitterProfileImageFromFxTwitter(tweetId: string) {
  try {
    const response = await fetch(`https://api.fxtwitter.com/status/${encodeURIComponent(tweetId)}`, {
      headers: TWITTER_PROFILE_LOOKUP_HEADERS,
      cache: "no-store"
    });

    if (!response.ok) {
      return null;
    }

    const payload = (await response.json()) as FxTwitterApiResponse;
    return normalizeTwitterProfileImageUrl(toSafeHttpUrl(payload.tweet?.author?.avatar_url));
  } catch {
    return null;
  }
}

async function fetchTwitterProfileImageFromVxTwitter(tweetId: string) {
  try {
    const response = await fetch(`https://api.vxtwitter.com/status/${encodeURIComponent(tweetId)}`, {
      headers: TWITTER_PROFILE_LOOKUP_HEADERS,
      cache: "no-store"
    });

    if (!response.ok) {
      return null;
    }

    const payload = (await response.json()) as VxTwitterApiResponse;
    return normalizeTwitterProfileImageUrl(toSafeHttpUrl(payload.user_profile_image_url));
  } catch {
    return null;
  }
}

async function enrichTwitterInfoWithProfileImage(
  info: TwitterVideoInfo,
  tweetIdCandidate: string | null
): Promise<TwitterVideoInfo> {
  if (!tweetIdCandidate) {
    return info;
  }

  const profileImageUrl =
    (await fetchTwitterProfileImageFromFxTwitter(tweetIdCandidate)) ||
    (await fetchTwitterProfileImageFromVxTwitter(tweetIdCandidate));

  return {
    ...info,
    uploaderProfileImageUrl: profileImageUrl
  };
}

function hasVideoStream(format: YoutubeDlFormat) {
  const videoCodec = toNonEmptyString(format.vcodec);
  if (videoCodec) {
    return videoCodec.toLowerCase() !== "none";
  }

  return (toNonNegativeNumber(format.height) || 0) > 0 || (toNonNegativeNumber(format.width) || 0) > 0;
}

function hasAudioStream(format: YoutubeDlFormat) {
  const audioCodec = toNonEmptyString(format.acodec);
  if (audioCodec) {
    return audioCodec.toLowerCase() !== "none";
  }

  return false;
}

function isLikelyVideoWithAudio(format: YoutubeDlFormat) {
  if (!hasVideoStream(format)) {
    return false;
  }

  if (hasAudioStream(format)) {
    return true;
  }

  const note = toNonEmptyString(format.format_note)?.toLowerCase() || "";
  if (note.includes("video only")) {
    return false;
  }

  const audioCodec = toNonEmptyString(format.acodec);
  if (audioCodec && audioCodec.toLowerCase() === "none") {
    return false;
  }

  return true;
}

function isHttpFormat(format: YoutubeDlFormat) {
  const protocol = toNonEmptyString(format.protocol)?.toLowerCase() || "";
  return protocol.includes("https") || protocol.includes("http");
}

function scoreFormat(format: YoutubeDlFormat) {
  const heightScore = toNonNegativeNumber(format.height) || 0;
  const bitrateScore = toNonNegativeNumber(format.tbr) || 0;
  const sizeScore = toNonNegativeNumber(format.filesize) || toNonNegativeNumber(format.filesize_approx) || 0;
  const extBonus = format.ext === "mp4" ? 2_500_000_000_000 : 0;
  const protocolBonus = isHttpFormat(format) ? 2_000_000_000_000 : 0;
  const audioBonus = isLikelyVideoWithAudio(format) ? 1_500_000_000_000 : 0;

  return extBonus + protocolBonus + audioBonus + heightScore * 1_000_000 + bitrateScore * 1_000 + sizeScore;
}

function pickBestFormat(formats: YoutubeDlFormat[]) {
  if (formats.length === 0) {
    return null;
  }

  return formats.reduce((best, current) => {
    if (!best) {
      return current;
    }

    return scoreFormat(current) > scoreFormat(best) ? current : best;
  }, null as YoutubeDlFormat | null);
}

function selectPreferredDownloadFormatId(payload: YoutubeDlPayload) {
  const selectedEntry = pickVideoEntry(payload);
  const formats = Array.isArray(selectedEntry.formats) ? selectedEntry.formats : [];

  const withVideoAndAudioHttpMp4 = formats.filter(
    (format) =>
      hasVideoStream(format) &&
      isLikelyVideoWithAudio(format) &&
      isHttpFormat(format) &&
      format.ext === "mp4"
  );
  const withVideoAndAudioMp4 = formats.filter(
    (format) => hasVideoStream(format) && isLikelyVideoWithAudio(format) && format.ext === "mp4"
  );
  const withVideoAndAudioHttp = formats.filter(
    (format) => hasVideoStream(format) && isLikelyVideoWithAudio(format) && isHttpFormat(format)
  );
  const withVideoAndAudioAny = formats.filter(
    (format) => hasVideoStream(format) && isLikelyVideoWithAudio(format)
  );
  const withVideoMp4 = formats.filter((format) => hasVideoStream(format) && format.ext === "mp4");
  const withVideoAny = formats.filter((format) => hasVideoStream(format));

  const preferredFormat =
    pickBestFormat(withVideoAndAudioHttpMp4) ||
    pickBestFormat(withVideoAndAudioMp4) ||
    pickBestFormat(withVideoAndAudioHttp) ||
    pickBestFormat(withVideoAndAudioAny) ||
    pickBestFormat(withVideoMp4) ||
    pickBestFormat(withVideoAny);

  const preferredFormatId = toNonEmptyString(preferredFormat?.format_id);
  if (!preferredFormatId) {
    throw new MediaProcessingError({
      message: "No downloadable video format was found for this tweet.",
      code: "invalid_input",
      statusCode: 400
    });
  }

  return preferredFormatId;
}

function toMediaProcessingError(error: unknown, fallbackMessage: string) {
  const resolvedError = error as YoutubeDlError;
  const toReadableText = (value: unknown) => {
    if (typeof value === "string") {
      const trimmedValue = value.trim();
      return trimmedValue || null;
    }

    if (value instanceof Buffer) {
      const trimmedValue = value.toString("utf8").trim();
      return trimmedValue || null;
    }

    return null;
  };

  const stderrText = toReadableText(resolvedError?.stderr) || "";
  const stderrTail = stderrText.split("\n").filter(Boolean).pop() || "";
  const shortMessage = toReadableText(resolvedError?.shortMessage) || "";
  const errorMessage = error instanceof Error ? error.message.trim() : "";
  const stringifiedError = error == null ? "" : String(error).trim();

  const readableMessage =
    stderrTail ||
    shortMessage ||
    errorMessage ||
    stringifiedError ||
    fallbackMessage;

  const lowerMessage = readableMessage.toLowerCase();
  const lowerCode = toReadableText(resolvedError?.code)?.toLowerCase() || "";

  if (lowerCode === "enoent") {
    const missingPath = toReadableText(resolvedError?.path);
    return new MediaProcessingError({
      message: missingPath
        ? `yt-dlp binary was not found at '${missingPath}'. Run npm install or set YT_DLP_PATH to a valid yt-dlp executable.`
        : "yt-dlp binary was not found. Run npm install or set YT_DLP_PATH to a valid yt-dlp executable.",
      code: "missing_binary",
      statusCode: 503
    });
  }

  const isNoVideoTweet =
    lowerMessage.includes("no video could be found in this tweet") ||
    lowerMessage.includes("this tweet does not contain a video") ||
    lowerMessage.includes("there is no video in this tweet");
  const isAccessIssue =
    lowerMessage.includes("private") ||
    lowerMessage.includes("login") ||
    lowerMessage.includes("sign in") ||
    lowerMessage.includes("unsupported url") ||
    lowerMessage.includes("not available");

  if (isNoVideoTweet) {
    return new MediaProcessingError({
      message: "This tweet does not contain downloadable video media.",
      code: "invalid_input",
      statusCode: 400
    });
  }

  return new MediaProcessingError({
    message:
      readableMessage && readableMessage.toLowerCase() !== "error" ? readableMessage : fallbackMessage,
    code: isAccessIssue ? "invalid_input" : "unknown",
    statusCode: isAccessIssue ? 400 : 502,
    details: stderrTail ? { stderrTail } : undefined
  });
}

async function resolveDownloadedOutputPath(initialOutputPath: string) {
  try {
    await fs.access(initialOutputPath);
    return initialOutputPath;
  } catch {
    const outputDirectory = path.dirname(initialOutputPath);
    const outputBaseName = path.basename(initialOutputPath, path.extname(initialOutputPath));
    const directoryEntries = await fs.readdir(outputDirectory);
    const candidateFiles = directoryEntries.filter((entry) => entry.startsWith(outputBaseName));

    if (candidateFiles.length === 0) {
      throw new MediaProcessingError({
        message: "Download completed but output file was not found.",
        code: "filesystem_failure",
        statusCode: 500
      });
    }

    const rankedCandidates = await Promise.all(
      candidateFiles.map(async (candidateFile) => {
        const candidatePath = path.join(outputDirectory, candidateFile);
        const candidateStats = await fs.stat(candidatePath);
        const candidateExtension = path.extname(candidateFile).toLowerCase();
        const extensionRank = candidateExtension === ".mp4" ? 3 : candidateExtension === ".webm" ? 2 : 1;

        return {
          candidatePath,
          extensionRank,
          size: candidateStats.size
        };
      })
    );

    rankedCandidates.sort((left, right) => {
      if (left.extensionRank !== right.extensionRank) {
        return right.extensionRank - left.extensionRank;
      }

      return right.size - left.size;
    });

    return rankedCandidates[0].candidatePath;
  }
}

async function fetchTwitterPayload(url: string) {
  const ytDlpClient = getYoutubeDlClient();
  const config = getMediaProcessingConfig();
  const authOptions = getYtDlpAuthOptions("twitter");

  return (await ytDlpClient(
    url,
    {
      skipDownload: true,
      dumpSingleJson: true,
      noWarnings: true,
      noPlaylist: true,
      noCheckCertificates: true,
      ...authOptions
    },
    {
      timeout: config.maxCommandDurationMs
    }
  )) as YoutubeDlPayload;
}

export async function fetchTwitterVideoInfo(url: string): Promise<TwitterVideoInfo> {
  try {
    const payload = await fetchTwitterPayload(url);
    const baseInfo = toTwitterVideoInfo(url, payload);
    return enrichTwitterInfoWithProfileImage(baseInfo, getTweetIdFromPayload(url, payload));
  } catch (error) {
    throw toMediaProcessingError(error, "Failed to fetch Twitter/X media info.");
  }
}

export async function downloadTwitterVideo(url: string): Promise<TwitterDownloadResult> {
  const ytDlpClient = getYoutubeDlClient();
  const config = getMediaProcessingConfig();
  const authOptions = getYtDlpAuthOptions("twitter");

  const payload = await fetchTwitterPayload(url);
  const info = await enrichTwitterInfoWithProfileImage(
    toTwitterVideoInfo(url, payload),
    getTweetIdFromPayload(url, payload)
  );
  const selectedFormatId = selectPreferredDownloadFormatId(payload);
  const initialOutputPath = await createTempOutputPath({
    extension: "mp4",
    prefix: "twitter-video"
  });

  try {
    await ytDlpClient(
      url,
      {
        format: selectedFormatId,
        output: initialOutputPath,
        noWarnings: true,
        noPlaylist: true,
        noCheckCertificates: true,
        forceOverwrites: true,
        ...authOptions
      },
      {
        timeout: config.maxCommandDurationMs
      }
    );

    const resolvedOutputPath = await resolveDownloadedOutputPath(initialOutputPath);
    assertPathWithinRoot(resolvedOutputPath, config.tempRootDirectory);

    const safeBaseName = sanitizeBaseName(`${info.title}-${Date.now().toString(36)}`);
    const fileName = `${safeBaseName}.mp4`;

    return {
      outputPath: resolvedOutputPath,
      fileName,
      info
    };
  } catch (error) {
    throw toMediaProcessingError(error, "Failed to download Twitter/X video.");
  }
}
