import { Buffer } from 'buffer';
import {
  DEFAULT_CONTENT_TYPE,
  MAX_DESIRED_APP_ORDER_PRICE,
  MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
} from '../config/config.js';
import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
import * as ipfs from '../utils/ipfs-service.js';
import {
  addressSchema,
  contentTypeSchema,
  emailContentSchema,
  emailSubjectSchema,
  labelSchema,
  positiveNumberSchema,
  senderNameSchema,
  throwIfMissing,
} from '../utils/validators.js';
import {
  PrepareEmailCampaignParams,
  PrepareEmailCampaignResponse,
} from './types.js';
import {
  DappAddressConsumer,
  DataProtectorConsumer,
  IExecConsumer,
  IpfsGatewayConfigConsumer,
  IpfsNodeConfigConsumer,
} from './internalTypes.js';

export type PrepareEmailCampaign = typeof prepareEmailCampaign;

export const prepareEmailCampaign = async ({
  iexec = throwIfMissing(),
  dataProtector = throwIfMissing(),
  workerpoolAddress,
  dappAddress,
  ipfsNode,
  ipfsGateway,
  senderName,
  emailSubject,
  emailContent,
  contentType = DEFAULT_CONTENT_TYPE,
  label,
  appMaxPrice = MAX_DESIRED_APP_ORDER_PRICE,
  workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
  grantedAccesses,
  maxProtectedDataPerTask,
}: IExecConsumer &
  DappAddressConsumer &
  IpfsNodeConfigConsumer &
  IpfsGatewayConfigConsumer &
  DataProtectorConsumer &
  PrepareEmailCampaignParams): Promise<PrepareEmailCampaignResponse> => {
  try {
    const vWorkerpoolAddress = addressSchema()
      .label('workerpoolAddress')
      .validateSync(workerpoolAddress);

    const vSenderName = senderNameSchema()
      .label('senderName')
      .validateSync(senderName);

    const vEmailSubject = emailSubjectSchema()
      .required()
      .label('emailSubject')
      .validateSync(emailSubject);

    const vEmailContent = emailContentSchema()
      .required()
      .label('emailContent')
      .validateSync(emailContent);

    const vContentType = contentTypeSchema()
      .label('contentType')
      .validateSync(contentType);

    const vLabel = labelSchema().label('label').validateSync(label);

    const vDappAddress = addressSchema()
      .required()
      .label('dappAddress')
      .validateSync(dappAddress);

    const vAppMaxPrice = positiveNumberSchema()
      .label('appMaxPrice')
      .validateSync(appMaxPrice);

    const vWorkerpoolMaxPrice = positiveNumberSchema()
      .label('workerpoolMaxPrice')
      .validateSync(workerpoolMaxPrice);

    const vMaxProtectedDataPerTask = positiveNumberSchema()
      .label('maxProtectedDataPerTask')
      .validateSync(maxProtectedDataPerTask);

    // TODO: factor this
    // Encrypt email content
    const emailContentEncryptionKey = iexec.dataset.generateEncryptionKey();
    const encryptedFile = await iexec.dataset
      .encrypt(Buffer.from(vEmailContent, 'utf8'), emailContentEncryptionKey)
      .catch((e) => {
        throw new WorkflowError({
          message: 'Failed to encrypt email content',
          errorCause: e,
        });
      });

    // Push email content to IPFS
    const cid = await ipfs
      .add(encryptedFile, {
        ipfsNode,
        ipfsGateway,
      })
      .catch((e) => {
        throw new WorkflowError({
          message: 'Failed to upload encrypted email content',
          errorCause: e,
        });
      });

    const multiaddr = `/ipfs/${cid}`;

    // Prepare secrets for the requester
    // Use a positive integer as secret ID (required by iexec)
    // Using "1" as a fixed ID for the requester secret
    const requesterSecretId = 1;
    const secrets = {
      [requesterSecretId]: JSON.stringify({
        emailSubject: vEmailSubject,
        emailContentMultiAddr: multiaddr,
        contentType: vContentType,
        senderName: vSenderName,
        emailContentEncryptionKey,
        useCallback: true,
      }),
    };

    // TODO: end factor this
    const { bulkRequest: campaignRequest } =
      await dataProtector.prepareBulkRequest({
        app: vDappAddress,
        appMaxPrice: vAppMaxPrice,
        workerpoolMaxPrice: vWorkerpoolMaxPrice,
        workerpool: vWorkerpoolAddress,
        args: vLabel,
        inputFiles: [],
        secrets,
        bulkAccesses: grantedAccesses,
        maxProtectedDataPerTask: vMaxProtectedDataPerTask,
      });

    return { campaignRequest };
  } catch (error) {
    // Protocol error detected, re-throwing as-is
    if ((error as any)?.isProtocolError === true) {
      throw error;
    }

    // Handle protocol errors - this will throw if it's an ApiCallError
    // handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true
    handleIfProtocolError(error);

    // For all other errors
    throw new WorkflowError({
      message: 'Failed to prepareEmailCampaign',
      errorCause: error,
    });
  }
};
