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 { YouTubeDownloadResult, YouTubeVideoInfo } from "@/types/media-processing";

interface YoutubeDlPayload {
  title?: string;
  webpage_url?: string;
  uploader?: string;
  uploader_id?: string | null;
  uploader_url?: string | null;
  channel?: string | null;
  channel_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;
}

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 toYouTubeVideoInfo(url: string, payload: YoutubeDlPayload): YouTubeVideoInfo {
  const selectedEntry = pickVideoEntry(payload);

  const title = toNonEmptyString(selectedEntry.title) || "YouTube Video";
  const webpageUrl = toNonEmptyString(selectedEntry.webpage_url) || url;
  const uploaderHandle = toNonEmptyString(selectedEntry.uploader_id) || toNonEmptyString(selectedEntry.channel);
  const uploaderProfileUrl =
    toNonEmptyString(selectedEntry.uploader_url) || toNonEmptyString(selectedEntry.channel_url);

  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 decodeHtmlAttribute(input: string) {
  return input
    .replaceAll("&amp;", "&")
    .replaceAll("&#x2F;", "/")
    .replaceAll("&#47;", "/")
    .replaceAll("&#x3D;", "=")
    .replaceAll("&#61;", "=")
    .replaceAll("&quot;", "\"")
    .replaceAll("&#x27;", "'")
    .replaceAll("&#39;", "'")
    .trim();
}

function getHtmlAttributeValue(tag: string, attribute: string) {
  const doubleQuoteMatch = tag.match(new RegExp(`${attribute}\\s*=\\s*\"([^\"]*)\"`, "i"));
  if (doubleQuoteMatch?.[1]) {
    return doubleQuoteMatch[1];
  }

  const singleQuoteMatch = tag.match(new RegExp(`${attribute}\\s*=\\s*'([^']*)'`, "i"));
  if (singleQuoteMatch?.[1]) {
    return singleQuoteMatch[1];
  }

  return null;
}

function extractMetaContentFromHtml(html: string, key: string) {
  const metaTags = html.match(/<meta\b[^>]*>/gi) ?? [];

  for (const metaTag of metaTags) {
    const property = getHtmlAttributeValue(metaTag, "property");
    const name = getHtmlAttributeValue(metaTag, "name");
    const content = getHtmlAttributeValue(metaTag, "content");

    if (!content) {
      continue;
    }

    if ((property && property.toLowerCase() === key) || (name && name.toLowerCase() === key)) {
      return decodeHtmlAttribute(content);
    }
  }

  return null;
}

function extractProfileImageFromHtml(html: string) {
  return (
    extractMetaContentFromHtml(html, "og:image") ||
    extractMetaContentFromHtml(html, "og:image:secure_url") ||
    extractMetaContentFromHtml(html, "twitter:image")
  );
}

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

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

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

const YOUTUBE_PROFILE_USER_AGENTS = [
  "Mozilla/5.0",
  "facebookexternalhit/1.1",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
] as const;

async function fetchYouTubeProfileImageUrl(profileUrl: string | null) {
  const safeProfileUrl = toSafeHttpUrl(profileUrl);
  if (!safeProfileUrl) {
    return null;
  }

  for (const userAgent of YOUTUBE_PROFILE_USER_AGENTS) {
    try {
      const response = await fetch(safeProfileUrl, {
        headers: {
          "user-agent": userAgent,
          accept: "text/html",
          "accept-language": "en-US,en;q=0.9"
        },
        cache: "no-store"
      });

      if (!response.ok) {
        continue;
      }

      const html = await response.text();
      const profileImageUrl = extractProfileImageFromHtml(html);
      if (profileImageUrl) {
        return profileImageUrl;
      }
    } catch {
      continue;
    }
  }

  return null;
}

async function enrichYouTubeInfoWithProfile(info: YouTubeVideoInfo): Promise<YouTubeVideoInfo> {
  const profileImageUrl = await fetchYouTubeProfileImageUrl(info.uploaderProfileUrl);

  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 YouTube URL.",
      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 isNoVideo =
    lowerMessage.includes("requested format is not available") ||
    lowerMessage.includes("this video is unavailable") ||
    lowerMessage.includes("private video") ||
    lowerMessage.includes("members-only") ||
    lowerMessage.includes("no video formats found") ||
    lowerMessage.includes("video unavailable");
  const isAccessIssue =
    lowerMessage.includes("sign in") ||
    lowerMessage.includes("age-restricted") ||
    lowerMessage.includes("login") ||
    lowerMessage.includes("unsupported url") ||
    lowerMessage.includes("not available");

  if (isNoVideo) {
    return new MediaProcessingError({
      message: "This YouTube video is unavailable or does not expose downloadable formats.",
      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
  });
}

function normalizeOutputVideoExtension(outputPath: string) {
  const extension = path.extname(outputPath).toLowerCase();

  if (extension === ".webm") {
    return "webm";
  }

  return "mp4";
}

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 fetchYouTubePayload(url: string) {
  const ytDlpClient = getYoutubeDlClient();
  const config = getMediaProcessingConfig();
  const authOptions = getYtDlpAuthOptions("youtube");

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

export async function fetchYoutubeVideoInfo(url: string): Promise<YouTubeVideoInfo> {
  try {
    const payload = await fetchYouTubePayload(url);
    const baseInfo = toYouTubeVideoInfo(url, payload);
    return enrichYouTubeInfoWithProfile(baseInfo);
  } catch (error) {
    throw toMediaProcessingError(error, "Failed to fetch YouTube media info.");
  }
}

export async function downloadYouTubeVideo(url: string): Promise<YouTubeDownloadResult> {
  const ytDlpClient = getYoutubeDlClient();
  const config = getMediaProcessingConfig();
  const authOptions = getYtDlpAuthOptions("youtube");

  const payload = await fetchYouTubePayload(url);
  const info = await enrichYouTubeInfoWithProfile(toYouTubeVideoInfo(url, payload));
  const selectedFormatId = selectPreferredDownloadFormatId(payload);
  const initialOutputPath = await createTempOutputPath({
    extension: "mp4",
    prefix: "youtube-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 outputExtension = normalizeOutputVideoExtension(resolvedOutputPath);
    const fileName = `${safeBaseName}.${outputExtension}`;

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