import { writable, get } from 'svelte/store';
import { env } from '$env/dynamic/public';

// Store state: undefined = not yet known, null = failed/logged out, string = token
export const accessToken = writable<string | null | undefined>(undefined);
export const isSignedIn = writable(false);
export const isGoogleApiReady = writable(false); // To track GAPI client readiness
export const userEmail = writable<string | null>(null);

let tokenClient: google.accounts.oauth2.TokenClient;
let gapiInited = false;
let gsiInited = false;

// This function ensures both GAPI (for Sheets/Drive APIs) and GSI (for auth) are loaded in the correct order.
export function initGoogleClients(callback: () => void) {
	// If everything is already initialized, just run the callback.
	if (gapiInited && gsiInited) {
		isGoogleApiReady.set(true); // Ensure it's set if called again
		callback();
		return;
	}

	// 1. Load GAPI script for Sheets/Drive APIs first.
	if (!gapiInited) {
		const gapiScript = document.createElement('script');
		gapiScript.src = 'https://apis.google.com/js/api.js';
		gapiScript.async = true;
		gapiScript.defer = true;
		document.head.appendChild(gapiScript);
		gapiScript.onload = () => {
			gapi.load('client', () => {
				gapi.client
					.init({
						discoveryDocs: [
							'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest',
							'https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest'
						]
					})
					.then(() => {
						gapiInited = true;
						// Now that GAPI is ready, initialize the GSI client.
						initGsiClient(callback);
					});
			});
		};
	} else {
		// GAPI is already ready, just ensure GSI is initialized.
		initGsiClient(callback);
	}
}

/**
 * Fetches user's email and stores it.
 */
async function fetchUserInfo(token: string) {
	try {
		const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
			headers: {
				Authorization: `Bearer ${token}`
			}
		});
		if (!response.ok) {
			throw new Error('Failed to fetch user info');
		}
		const profile = await response.json();
		userEmail.set(profile.email);
	} catch (error) {
		console.error('Error fetching user info:', error);
		userEmail.set(null);
	}
}

// 2. Load GSI script for Auth. This should only be called after GAPI is ready.
function initGsiClient(callback: () => void) {
	if (gsiInited) {
		callback();
		return;
	}
	const gsiScript = document.createElement('script');
	gsiScript.src = 'https://accounts.google.com/gsi/client';
	gsiScript.async = true;
	gsiScript.defer = true;
	document.head.appendChild(gsiScript);
	gsiScript.onload = () => {
		gsiInited = true;
		tokenClient = google.accounts.oauth2.initTokenClient({
			client_id: env.PUBLIC_GOOGLE_CLIENT_ID,
			scope:
				'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly https://www.googleapis.com/auth/userinfo.email',
			callback: (tokenResponse) => {
				// This callback handles responses from all token requests.
				if (tokenResponse.error) {
					console.error('Google token error:', tokenResponse.error);
					accessToken.set(null);
					isSignedIn.set(false);
					if (gapiInited) gapi.client.setToken(null);
				} else if (tokenResponse.access_token) {
					const token = tokenResponse.access_token;
					accessToken.set(token);
					isSignedIn.set(true);
					// Also set the token for the GAPI client
					if (gapiInited) gapi.client.setToken({ access_token: token });
					fetchUserInfo(token);
				}
			}
		});
		isGoogleApiReady.set(true);
		callback();
	};
}

/**
 * Tries to get a token silently.
 * This is for background tasks and on-load checks.
 * It will not show a consent prompt to the user.
 */
export function ensureToken(): Promise<string> {
	return new Promise((res, rej) => {
		initGoogleClients(() => {
			const currentToken = get(accessToken);
			// If we already have a valid token, resolve immediately.
			if (currentToken) {
				res(currentToken);
				return;
			}

			let unsubscribe: () => void;
			unsubscribe = accessToken.subscribe((t) => {
				// undefined means we are still waiting for the initial token request.
				if (t) { // Got a token.
					if (unsubscribe) unsubscribe();
					res(t);
				} else if (t === null) { // Got an explicit null, meaning auth failed.
					if (unsubscribe) unsubscribe();
					rej(new Error('Failed to retrieve access token. The user may need to sign in.'));
				}
			});

			// If no token, request one silently.
			// The result is handled by the callback in initGsiClient, which updates the store and resolves the promise.
			if (get(accessToken) === undefined) {
				tokenClient.requestAccessToken({ prompt: '' });
			}
		});
	});
}

/**
 * Prompts the user for consent to grant a token.
 * This should be called when a user clicks a "Sign In" button.
 */
export function requestTokenFromUser() {
	initGoogleClients(() => {
		if (tokenClient) {
			tokenClient.requestAccessToken({ prompt: 'consent' });
		} else {
			console.error("requestTokenFromUser called before Google client was initialized.");
		}
	});
}

/**
 * Signs the user out, revokes the token, and clears all local state.
 */
export function handleSignOut() {
	const token = get(accessToken);
	if (token && gsiInited) {
		google.accounts.oauth2.revoke(token, () => {
			console.log('User token revoked.');
		});
	}

	// Clear all tokens and states
	if (gapiInited) {
		gapi.client.setToken(null);
	}
	accessToken.set(null);
	isSignedIn.set(false);
	userEmail.set(null);

	console.log('User signed out.');
}

export async function searchSheets(query: string) {
  await ensureToken(); // Ensure we are authenticated before making a call
  if (!gapi.client || !gapi.client.drive) {
    throw new Error('Google Drive API not loaded');
  }
  const response = await gapi.client.drive.files.list({
    q: `mimeType='application/vnd.google-apps.spreadsheet' and name contains '${query}'`,
    fields: 'files(id, name, iconLink, webViewLink)',
    pageSize: 20,
    supportsAllDrives: true,
    includeItemsFromAllDrives: true,
    corpora: 'allDrives'
  });
  return response.result.files || [];
}

export async function getSheetNames(spreadsheetId: string) {
  await ensureToken();
  if (!gapi.client || !gapi.client.sheets) {
    throw new Error('Google Sheets API not loaded');
  }
  const response = await gapi.client.sheets.spreadsheets.get({
    spreadsheetId,
    fields: 'sheets.properties'
  });
  
  if (!response.result.sheets) {
    return [];
  }

  return response.result.sheets.map(sheet => sheet.properties?.title || '');
}

export async function getSheetData(spreadsheetId: string, range: string) {
    await ensureToken();
    if (!gapi.client || !gapi.client.sheets) {
        throw new Error('Google Sheets API not loaded');
    }
    const response = await gapi.client.sheets.spreadsheets.values.get({
        spreadsheetId,
        range,
    });
    return response.result.values || [];
}

// Extract Google Drive file ID from various URL formats
export function extractDriveFileId(url: string): string | null {
  if (!url) return null;
  
  // Handle different Google Drive URL formats
  const patterns = [
    /\/file\/d\/([a-zA-Z0-9-_]+)/,           // https://drive.google.com/file/d/FILE_ID/view
    /id=([a-zA-Z0-9-_]+)/,                   // https://drive.google.com/open?id=FILE_ID
    /\/d\/([a-zA-Z0-9-_]+)/,                 // https://drive.google.com/uc?id=FILE_ID&export=download
    /^([a-zA-Z0-9-_]{25,})$/                 // Direct file ID
  ];
  
  for (const pattern of patterns) {
    const match = pattern.exec(url);
    if (match) {
      return match[1];
    }
  }
  
  return null;
}

// Check if URL is a Google Drive URL
export function isGoogleDriveUrl(url: string): boolean {
  return url.includes('drive.google.com') || url.includes('googleapis.com');
}

// Download image from Google Drive using the API
export async function downloadDriveImage(url: string): Promise<Blob> {
  await ensureToken();
  const fileId = extractDriveFileId(url);
  
  if (!fileId) {
    throw new Error('Could not extract file ID from Google Drive URL');
  }
  
  if (!gapi.client || !gapi.client.drive) {
    throw new Error('Google Drive API not loaded');
  }
  
  try {
    // Get file metadata first to check if it exists and is accessible
    const metadata = await gapi.client.drive.files.get({
      fileId: fileId,
      fields: 'id,name,mimeType,size'
    });
    
    if (!metadata.result.mimeType?.startsWith('image/')) {
      throw new Error('File is not an image');
    }
    
    // Download the file content
    const response = await gapi.client.drive.files.get({
      fileId: fileId,
      alt: 'media'
    });
    
    // The response body is already binary data, convert to blob
    const binaryString = response.body;
    const bytes = new Uint8Array(binaryString.length);
    for (let i = 0; i < binaryString.length; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }
    
    return new Blob([bytes], { type: metadata.result.mimeType });
    
  } catch (error) {
    console.error('Error downloading from Google Drive:', error);
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new Error(`Failed to download image from Google Drive: ${errorMessage}`);
  }
}

// Create an object URL from image data for display
export function createImageObjectUrl(blob: Blob): string {
  return URL.createObjectURL(blob);
}
