// src/services/onedrive.ts
import "server-only";
import { getFreshAccessToken } from "@/lib/auth-utils";
import { auth } from "@/auth";

/**
 * PRIVATE HELPER: graphRequest
 * This internal function handles the heavy lifting of fetching tokens
 * and making the actual HTTP call to Microsoft.
 */
async function graphRequest(userId: string, endpoint: string, options: RequestInit = {}) {
  // 1. Automatically handle token refresh logic
  const token = await getFreshAccessToken(userId);
  const baseUrl = "https://graph.microsoft.com/v1.0";
  
  const res = await fetch(`${baseUrl}${endpoint}`, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${token}`,
    },
  });

  // 2. Centralized Error Handling for OneDrive
  if (!res.ok) {
    const errorData = await res.text();
    console.error(`OneDrive API Error [${endpoint}]:`, errorData);
    throw new Error(`OneDrive API failed: ${res.statusText}`);
  }

  return res;
}

/**
 * SERVICE: Download File Content
 * Returns the raw binary stream from OneDrive.
 */
export async function getOneDriveContentStream(userId: string, oneDriveId: string) {
  return await graphRequest(userId, `/me/drive/items/${oneDriveId}/content`);
}

/**
 * SERVICE: Get File Metadata
 * Used to get the @microsoft.graph.downloadUrl or driveItem properties.
 */
export async function getOneDriveItem(userId: string, oneDriveId: string) {
  const res = await graphRequest(userId, `/me/drive/items/${oneDriveId}`);
  return res.json();
}

/**
 * SERVICE: Upload File
 * Handles the PUT request to OneDrive for new or updated files.
 */
export async function uploadToOneDrive(userId: string, file: File, oneDriveId?: string) {
  // If oneDriveId exists, we update. Otherwise, we'd use a path (needs expansion for new files).
  const endpoint = oneDriveId 
    ? `/me/drive/items/${oneDriveId}/content` 
    : `/me/drive/root:/${file.name}:/content`;

  return await graphRequest(userId, endpoint, {
    method: "PUT",
    headers: { "Content-Type": file.type },
    body: Buffer.from(await file.arrayBuffer()),
  });
}

/**
 * SERVICE: Delete from Cloud
 */
export async function deleteFromOneDrive(userId: string, oneDriveId: string) {
  return await graphRequest(userId, `/me/drive/items/${oneDriveId}`, {
    method: "DELETE",
  });
}

/**
 * SERVICE: List Children of the WebCalibre folder
 */
export async function getWebCalibreChildren(userId: string) {
  const res = await graphRequest(userId, "/me/drive/root:/WebCalibre:/children");
  const data = await res.json();
  return data.value; // Returns the array of driveItems
}

/**
 * SERVICE: Ensure a specific folder exists in OneDrive
 * Returns the folder ID
 */
export async function ensureOneDriveFolder(userId: string, folderName: string) {
  try {
    const res = await graphRequest(userId, `/me/drive/root:/${folderName}`);
    const data = await res.json();
    return data.id;
  } catch (error) {
    // If 404, create it
    const createRes = await graphRequest(userId, `/me/drive/root/children`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: folderName, folder: {} })
    });
    const data = await createRes.json();
    return data.id;
  }
}

/**
 * SERVICE: Upload Large File via Session
 * This replaces the basic PUT for better reliability
 */
export async function uploadLargeFile(userId: string, file: File, folderName: string) {
  // 1. Create Upload Session
  const sessionRes = await graphRequest(userId, `/me/drive/root:/${folderName}/${file.name}:/createUploadSession`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "rename" } })
  });
  const { uploadUrl } = await sessionRes.json();

  // 2. Upload the data to the provided URL (No Authorization header needed for the uploadUrl itself)
  const buffer = Buffer.from(await file.arrayBuffer());
  const uploadRes = await fetch(uploadUrl, {
    method: "PUT",
    headers: { 
      "Content-Length": `${file.size}`,
      "Content-Range": `bytes 0-${file.size - 1}/${file.size}` 
    },
    body: buffer
  });

  if (!uploadRes.ok) throw new Error("Upload session failed");
  return await uploadRes.json(); // Returns the DriveItem
}
/**
 * SERVICE: Create a folder by name inside a parent path
 */
export async function createOneDriveFolder(userId: string, parentPath: string, folderName: string) {
  return await graphRequest(userId, `/me/drive/root:/${parentPath}:/children`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ 
      name: folderName, 
      folder: {},
      "@microsoft.graph.conflictBehavior": "fail" 
    })
  });
}

/**
 * SERVICE: Upload to a specific folder ID (using session)
 */
export async function uploadToFolderId(userId: string, file: File, folderId: string) {
  const sessionRes = await graphRequest(userId, `/me/drive/items/${folderId}:/${encodeURIComponent(file.name)}:/createUploadSession`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ item: { "@microsoft.graph.conflictBehavior": "replace" } })
  });
  
  const { uploadUrl } = await sessionRes.json();
  const buffer = Buffer.from(await file.arrayBuffer());

  const uploadRes = await fetch(uploadUrl, {
    method: "PUT",
    headers: { 
      "Content-Length": `${file.size}`, 
      "Content-Range": `bytes 0-${file.size - 1}/${file.size}` 
    },
    body: buffer
  });

  if (!uploadRes.ok) throw new Error("Upload failed");
  return await uploadRes.json();
}



/**
 * Retrieves the access token from the active NextAuth session.
 * This is required to authorize requests to the Microsoft Graph API.
 */
async function getAccessToken(): Promise<string> {
  const session = await auth();
  
  // We cast to 'any' because the default Session type often 
  // needs custom augmentation to show the accessToken.
  const token = (session as any)?.accessToken;

  if (!token) {
    // This will help you debug if the session is missing the token
    console.error("OneDrive Service Error: No access token found in session.");
    throw new Error("Authentication required: No access token available.");
  }

  return token;
}

/**
 * Fetches raw file content from OneDrive.
 * Parameterized token allows this to be used in different contexts (User actions, Webhooks, etc.)
 */
export async function getOneDriveFileBuffer(oneDriveId: string, token: string): Promise<Buffer> {
  const response = await fetch(
    `https://graph.microsoft.com/v1.0/me/drive/items/${oneDriveId}/content`,
    {
      method: 'GET', // Explicit is better
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Accept': '*/*' 
      },
      // CRITICAL: Next.js tends to cache fetch calls. 
      // We do NOT want to cache large binary buffers in memory/disk.
      cache: 'no-store', 
    }
  );

  if (!response.ok) {
    const errorBody = await response.text().catch(() => "No error body");
    console.error(`OneDrive Download Error (${response.status}):`, errorBody);
    throw new Error(`OneDrive download failed: ${response.statusText}`);
  }

  const arrayBuffer = await response.arrayBuffer();
  return Buffer.from(arrayBuffer);
}