import { FilesService, TriggerStrategy, createTrigger,  Property } from '@activepieces/pieces-framework';
import { getGraphBaseUrl } from '../common/microsoft-cloud';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Message, FileAttachment } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { microsoftOutlookAuth } from '../common/auth';
import { mailFolderIdDropdown } from '../common/props';
import { isNil } from '@activepieces/pieces-framework';

async function enrichAttachments(
	client: Client,
	messages: Message[],
	files: FilesService,
	nameFilter?: string,
	senderFilter?: string,
): Promise<Record<string, any>[]> {
	const attachments: Record<string, any>[] = [];

	for (const message of messages) {
		if (senderFilter && message.sender?.emailAddress?.address && !message.sender.emailAddress.address.toLowerCase().includes(senderFilter.toLowerCase())) {
			continue;
		}

		const attachmentResponse: PageCollection = await client
			.api(`/me/messages/${message.id}/attachments`)
			.get();

		for (const attachment of attachmentResponse.value as FileAttachment[]) {
			const { contentBytes, ...rest } = attachment;

			if (nameFilter && attachment.name && !attachment.name.toLowerCase().includes(nameFilter.toLowerCase())) {
				continue;
			}

			if (attachment.name && contentBytes) {
				const file = await files.write({
					fileName: attachment.name,
					data: Buffer.from(contentBytes, 'base64'),
				});

				attachments.push({
					file,
					messageId: message.id!,
					messageSubject: message.subject,
					messageSender: message.sender,
					messageReceivedDateTime: message.receivedDateTime,
					parentFolderId: message.parentFolderId,
					...rest,
				});
			}
		}
	}
	return attachments;
}

export const newAttachmentTrigger = createTrigger({
	auth: microsoftOutlookAuth,
	name: 'newAttachment',
	displayName: 'New Attachment',
	description: 'Triggers when a new email containing one or more attachments arrives.',
	aiMetadata: {
		description: 'Fires once per attachment when a new email carrying one or more file attachments arrives, optionally scoped to a folder, sender, or attachment-name filter. Each fire represents a single attachment from a newly received message.',
	},
	props: {
		folderId: mailFolderIdDropdown({
			displayName: 'Folder',
			description: 'Monitor attachments in a specific folder. Leave empty to monitor all folders.',
			required: false,
		}),
		sender: Property.ShortText({
			displayName: 'From (Sender Email)',
			description: 'Filter emails from a specific sender (optional). Leave empty to for all senders.',
			required: false,
		}),
		attachmentNameFilter: Property.ShortText({
			displayName: 'Attachment Name Filter',
			description: 'Filter attachments by name (contains). Leave empty to include all attachments.',
			required: false,			
		}),
	},
	sampleData: {},
	type: TriggerStrategy.POLLING,
	async onEnable(context) {
		await context.store.put('lastPoll', Date.now());
	},
	async onDisable(context) {
		// return
	},
	async test(context) {
		const { folderId, attachmentNameFilter, sender } = context.propsValue;
		const cloud = context.auth.props?.['cloud'] as string | undefined;
		const client = Client.initWithMiddleware({
			authProvider: {
				getAccessToken: () => Promise.resolve(context.auth.access_token),
			},
			baseUrl: getGraphBaseUrl(cloud),
		});
		const baseUrl = folderId ? `/me/mailFolders/${folderId}/messages` : '/me/messages';

		const response: PageCollection = await client
			.api(`${baseUrl}?$filter=hasAttachments eq true`)
			.top(10)
			.get();

		const attachments = await enrichAttachments(client, response.value as Message[], context.files, attachmentNameFilter, sender);

		const items = attachments.map((attachment) => ({
			epochMilliSeconds: dayjs(attachment['messageReceivedDateTime']).valueOf(),
			data: attachment,
		}));

		return items.map((item) => item.data);
	},
	async run(context) {
		const lastFetchEpochMS = await context.store.get<number>('lastPoll');
		if (isNil(lastFetchEpochMS)) {
			throw new Error("lastPoll doesn't exist in the store.");
		}

		const { folderId, attachmentNameFilter, sender } = context.propsValue;
		const cloud = context.auth.props?.['cloud'] as string | undefined;
		const client = Client.initWithMiddleware({
			authProvider: {
				getAccessToken: () => Promise.resolve(context.auth.access_token),
			},
			baseUrl: getGraphBaseUrl(cloud),
		});

		const baseUrl = folderId ? `/me/mailFolders/${folderId}/messages` : '/me/messages';
		let response: PageCollection = await client
			.api(
				`${baseUrl}?$filter=receivedDateTime gt ${dayjs(
					lastFetchEpochMS,
				).toISOString()} and hasAttachments eq true`,
			)
			.orderby('receivedDateTime desc')
			.get();

		const messages: Message[] = [];

		while (response.value.length > 0) {
			messages.push(...(response.value as Message[]));

			if (response['@odata.nextLink']) {
				response = await client.api(response['@odata.nextLink']).get();
			} else {
				break;
			}
		}
		const attachments = await enrichAttachments(client, messages, context.files, attachmentNameFilter, sender);

		const items = attachments.map((attachment) => ({
			epochMilliSeconds: dayjs(attachment['messageReceivedDateTime']).valueOf(),
			data: attachment,
		}));

		const newLastEpochMilliSeconds = items.reduce(
			(acc, item) => Math.max(acc, item.epochMilliSeconds),
			lastFetchEpochMS,
		);
		await context.store.put('lastPoll', newLastEpochMilliSeconds);
		return items.filter((f) => f.epochMilliSeconds > lastFetchEpochMS).map((item) => item.data);
	},
});
