import { google } from "googleapis";
import fs from "fs";
import { prisma } from "../db";
import { parseTags } from "../constants";
import { decryptSecret, encryptSecret } from "../crypto/secrets";
import type { JobItem } from "@prisma/client";
import { formatYouTubeErrorForUser } from "./errors";

async function refreshAccessToken(refreshToken: string) {
  const oauth2Client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET,
  );
  oauth2Client.setCredentials({ refresh_token: refreshToken });
  const { credentials } = await oauth2Client.refreshAccessToken();
  return credentials;
}

export async function getYouTubeClient(userId: string) {
  const connection = await prisma.youTubeConnection.findUnique({
    where: { userId },
  });
  if (!connection) throw new Error("YouTube account not connected");

  const oauth2Client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET,
  );

  let accessToken = decryptSecret(connection.accessToken);
  const refreshToken = decryptSecret(connection.refreshToken);
  let expiresAt = connection.expiresAt;

  if (new Date() >= expiresAt) {
    const credentials = await refreshAccessToken(refreshToken);
    if (!credentials.access_token) {
      throw new Error("Failed to refresh YouTube access token");
    }
    accessToken = credentials.access_token;
    expiresAt = credentials.expiry_date
      ? new Date(credentials.expiry_date)
      : new Date(Date.now() + 3600 * 1000);

    await prisma.youTubeConnection.update({
      where: { userId },
      data: {
        accessToken: encryptSecret(accessToken),
        // Re-encrypt refresh token if it was still plaintext legacy
        refreshToken: encryptSecret(refreshToken),
        expiresAt,
      },
    });
  }

  oauth2Client.setCredentials({
    access_token: accessToken,
    refresh_token: refreshToken,
  });

  return google.youtube({ version: "v3", auth: oauth2Client });
}

export async function uploadToYouTube(
  userId: string,
  videoPath: string,
  item: JobItem,
): Promise<string> {
  try {
    const youtube = await getYouTubeClient(userId);
    const tags = parseTags(item.tags);

    const privacyStatus =
      item.privacy === "PUBLIC"
        ? "public"
        : item.privacy === "PRIVATE"
          ? "private"
          : "unlisted";

    const response = await youtube.videos.insert({
      part: ["snippet", "status"],
      notifySubscribers: item.notifySubscribers,
      requestBody: {
        snippet: {
          title: item.title,
          description: item.description,
          tags: tags.length > 0 ? tags : undefined,
          categoryId: item.categoryId,
        },
        status: {
          privacyStatus,
          embeddable: item.embeddable,
          selfDeclaredMadeForKids: item.madeForKids,
          license: item.creativeCommons ? "creativeCommon" : "youtube",
        },
      },
      media: {
        body: fs.createReadStream(videoPath),
      },
    });

    const videoId = response.data.id;
    if (!videoId) throw new Error("YouTube upload succeeded but no video ID returned");

    if (item.playlistId) {
      try {
        await addVideoToPlaylist(youtube, item.playlistId, videoId);
      } catch (playlistErr) {
        throw new Error(
          `Video uploaded (${videoId}) but failed to add to playlist: ${formatYouTubeErrorForUser(playlistErr)}`,
        );
      }
    }

    return videoId;
  } catch (err) {
    if (err instanceof Error && err.message.startsWith("Video uploaded (")) {
      throw err;
    }
    throw new Error(formatYouTubeErrorForUser(err));
  }
}

export type PlaylistPrivacy = "public" | "unlisted" | "private";

export type CreatePlaylistInput = {
  title: string;
  description?: string;
  privacy?: PlaylistPrivacy;
};

function toPlaylistPrivacyStatus(privacy?: PlaylistPrivacy) {
  if (privacy === "public") return "public";
  if (privacy === "unlisted") return "unlisted";
  return "private";
}

export async function listYouTubePlaylists(userId: string) {
  const youtube = await getYouTubeClient(userId);
  const playlists: Array<{ id: string; title: string; itemCount: number }> = [];
  let pageToken: string | undefined;

  do {
    const response = await youtube.playlists.list({
      part: ["snippet", "contentDetails"],
      mine: true,
      maxResults: 50,
      pageToken,
    });

    for (const playlist of response.data.items ?? []) {
      if (!playlist.id || !playlist.snippet?.title) continue;
      playlists.push({
        id: playlist.id,
        title: playlist.snippet.title,
        itemCount: playlist.contentDetails?.itemCount ?? 0,
      });
    }

    pageToken = response.data.nextPageToken ?? undefined;
  } while (pageToken);

  return playlists;
}

export async function createYouTubePlaylist(userId: string, input: CreatePlaylistInput) {
  const title = input.title?.trim();
  if (!title) throw new Error("Playlist title is required");

  try {
    const youtube = await getYouTubeClient(userId);
    const response = await youtube.playlists.insert({
      part: ["snippet", "status"],
      requestBody: {
        snippet: {
          title,
          description: input.description?.trim() || "",
        },
        status: {
          privacyStatus: toPlaylistPrivacyStatus(input.privacy),
        },
      },
    });

    const id = response.data.id;
    if (!id) throw new Error("YouTube created the playlist but returned no ID");

    return {
      id,
      title: response.data.snippet?.title || title,
      itemCount: 0,
      privacy: input.privacy ?? "private",
    };
  } catch (err) {
    throw new Error(formatYouTubeErrorForUser(err));
  }
}

async function addVideoToPlaylist(
  youtube: Awaited<ReturnType<typeof getYouTubeClient>>,
  playlistId: string,
  videoId: string,
) {
  await youtube.playlistItems.insert({
    part: ["snippet"],
    requestBody: {
      snippet: {
        playlistId,
        resourceId: {
          kind: "youtube#video",
          videoId,
        },
      },
    },
  });
}

export async function fetchYouTubeChannel(accessToken: string, refreshToken: string) {
  const oauth2Client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET,
  );
  oauth2Client.setCredentials({ access_token: accessToken, refresh_token: refreshToken });
  const youtube = google.youtube({ version: "v3", auth: oauth2Client });
  const response = await youtube.channels.list({ part: ["snippet"], mine: true });
  const channel = response.data.items?.[0];
  if (!channel?.id) throw new Error("No YouTube channel found for this account");
  return {
    channelId: channel.id,
    channelTitle: channel.snippet?.title || "Unknown Channel",
  };
}
