import { Analytics } from "../../analytics.js";

import type {
  EndUserAccount,
  AddEndUserEvmAccountResult,
  AddEndUserEvmSmartAccountResult,
  AddEndUserSolanaAccountResult,
  AddEvmSmartAccountOptions,
  GetDelegationForEndUserResult,
  GetDelegationForEndUserAccountResult,
  AccountGetDelegationForAccountOptions,
  AccountRevokeDelegationForAccountOptions,
  SignEvmTransactionResult,
  SignEvmMessageResult,
  SignEvmTypedDataResult,
  SendEvmTransactionResult,
  SendEvmAssetResult,
  SendUserOperationResult,
  CreateEvmEip7702DelegationForEndUserResult,
  SignSolanaMessageResult,
  SignSolanaTransactionResult,
  SendSolanaTransactionResult,
  SendSolanaAssetResult,
  AccountSignEvmTransactionOptions,
  AccountSignEvmMessageOptions,
  AccountSignEvmTypedDataOptions,
  AccountSendEvmTransactionOptions,
  AccountSendEvmAssetOptions,
  AccountSendUserOperationOptions,
  AccountCreateEvmEip7702DelegationOptions,
  AccountSignSolanaMessageOptions,
  AccountSignSolanaTransactionOptions,
  AccountSendSolanaTransactionOptions,
  AccountSendSolanaAssetOptions,
} from "./endUser.types.js";
import type {
  CdpOpenApiClientType,
  EndUser as OpenAPIEndUser,
} from "../../openapi-client/index.js";

/**
 * Options for converting an OpenAPI EndUser to an EndUserAccount with actions.
 */
export type ToEndUserAccountOptions = {
  /** The end user from the API response. */
  endUser: OpenAPIEndUser;
};

/**
 * Resolves the first EVM EOA address for this end user, or throws if none exist and no override was provided.
 *
 * @param endUser - The OpenAPI end user.
 * @param override - An optional address override.
 * @returns The resolved EVM address.
 */
function resolveEvmAddress(endUser: OpenAPIEndUser, override?: string): string {
  const address = override ?? endUser.evmAccountObjects[0]?.address;
  if (!address) {
    throw new Error(
      "No EVM account found on this end user. Provide an explicit address or add an EVM account first.",
    );
  }
  return address;
}

/**
 * Resolves the first EVM smart account address for this end user, or throws if none exist and no override was provided.
 *
 * @param endUser - The OpenAPI end user.
 * @param override - An optional address override.
 * @returns The resolved EVM smart account address.
 */
function resolveEvmSmartAccountAddress(endUser: OpenAPIEndUser, override?: string): string {
  const address = override ?? endUser.evmSmartAccountObjects[0]?.address;
  if (!address) {
    throw new Error(
      "No EVM smart account found on this end user. Provide an explicit address or add an EVM smart account first.",
    );
  }
  return address;
}

/**
 * Resolves the first Solana address for this end user, or throws if none exist and no override was provided.
 *
 * @param endUser - The OpenAPI end user.
 * @param override - An optional address override.
 * @returns The resolved Solana address.
 */
function resolveSolanaAddress(endUser: OpenAPIEndUser, override?: string): string {
  const address = override ?? endUser.solanaAccountObjects[0]?.address;
  if (!address) {
    throw new Error(
      "No Solana account found on this end user. Provide an explicit address or add a Solana account first.",
    );
  }
  return address;
}

/**
 * Creates an EndUserAccount instance with actions from an existing OpenAPI EndUser.
 * This wraps the raw API response and adds convenience methods for adding accounts
 * and performing delegated signing/sending operations.
 *
 * @param apiClient - The API client.
 * @param options - Configuration options.
 * @param options.endUser - The end user from the API response.
 * @returns An EndUserAccount instance with action methods.
 */
export function toEndUserAccount(
  apiClient: CdpOpenApiClientType,
  options: ToEndUserAccountOptions,
): EndUserAccount {
  const { endUser } = options;

  const endUserAccount: EndUserAccount = {
    // Pass through all properties from the OpenAPI EndUser
    userId: endUser.userId,
    authenticationMethods: endUser.authenticationMethods,
    mfaMethods: endUser.mfaMethods,
    evmAccounts: endUser.evmAccounts,
    evmAccountObjects: endUser.evmAccountObjects,
    evmSmartAccounts: endUser.evmSmartAccounts,
    evmSmartAccountObjects: endUser.evmSmartAccountObjects,
    solanaAccounts: endUser.solanaAccounts,
    solanaAccountObjects: endUser.solanaAccountObjects,
    createdAt: endUser.createdAt,

    // ─── Account Management Methods ───

    async addEvmAccount(idempotencyKey?: string): Promise<AddEndUserEvmAccountResult> {
      Analytics.trackAction({ action: "end_user_add_evm_account" });
      return apiClient.addEndUserEvmAccount(endUser.userId, {}, idempotencyKey);
    },

    async addEvmSmartAccount(
      smartAccountOptions: AddEvmSmartAccountOptions,
    ): Promise<AddEndUserEvmSmartAccountResult> {
      Analytics.trackAction({ action: "end_user_add_evm_smart_account" });
      return apiClient.addEndUserEvmSmartAccount(
        endUser.userId,
        { enableSpendPermissions: smartAccountOptions.enableSpendPermissions },
        smartAccountOptions.idempotencyKey,
      );
    },

    async addSolanaAccount(idempotencyKey?: string): Promise<AddEndUserSolanaAccountResult> {
      Analytics.trackAction({ action: "end_user_add_solana_account" });
      return apiClient.addEndUserSolanaAccount(endUser.userId, {}, idempotencyKey);
    },

    async getDelegation(): Promise<GetDelegationForEndUserResult> {
      Analytics.trackAction({ action: "end_user_get_delegation" });
      return apiClient.getDelegationForEndUser(endUser.userId);
    },

    async revokeDelegation(idempotencyKey?: string): Promise<void> {
      Analytics.trackAction({ action: "end_user_revoke_delegation" });
      await apiClient.revokeDelegationForEndUser(endUser.userId, {}, undefined, idempotencyKey);
    },

    // ─── Account-Scoped Delegation Methods ───

    async getDelegationForAccount(
      opts: AccountGetDelegationForAccountOptions,
    ): Promise<GetDelegationForEndUserAccountResult> {
      Analytics.trackAction({ action: "end_user_get_delegation_for_account" });
      const address = resolveEvmAddress(endUser, opts.address);
      return apiClient.getDelegationForEndUserAccount(endUser.userId, address);
    },

    async revokeDelegationForAccount(
      opts: AccountRevokeDelegationForAccountOptions,
    ): Promise<void> {
      Analytics.trackAction({ action: "end_user_revoke_delegation_for_account" });
      const address = resolveEvmAddress(endUser, opts.address);
      await apiClient.revokeDelegationForEndUserAccount(
        endUser.userId,
        address,
        {},
        undefined,
        opts.idempotencyKey,
      );
    },

    // ─── Delegated EVM Sign Methods ───

    async signEvmTransaction(
      opts: AccountSignEvmTransactionOptions,
    ): Promise<SignEvmTransactionResult> {
      Analytics.trackAction({ action: "end_user_sign_evm_transaction" });
      const address = resolveEvmAddress(endUser, opts.address);
      return apiClient.signEvmTransactionWithEndUserAccount(
        endUser.userId,
        { address, transaction: opts.transaction },
        undefined,
        opts.idempotencyKey,
      );
    },

    async signEvmMessage(opts: AccountSignEvmMessageOptions): Promise<SignEvmMessageResult> {
      Analytics.trackAction({ action: "end_user_sign_evm_message" });
      const address = resolveEvmAddress(endUser, opts.address);
      return apiClient.signEvmMessageWithEndUserAccount(
        endUser.userId,
        { address, message: opts.message },
        undefined,
        opts.idempotencyKey,
      );
    },

    async signEvmTypedData(opts: AccountSignEvmTypedDataOptions): Promise<SignEvmTypedDataResult> {
      Analytics.trackAction({ action: "end_user_sign_evm_typed_data" });
      const address = resolveEvmAddress(endUser, opts.address);
      return apiClient.signEvmTypedDataWithEndUserAccount(
        endUser.userId,
        { address, typedData: opts.typedData },
        undefined,
        opts.idempotencyKey,
      );
    },

    // ─── Delegated EVM Send Methods ───

    async sendEvmTransaction(
      opts: AccountSendEvmTransactionOptions,
    ): Promise<SendEvmTransactionResult> {
      Analytics.trackAction({ action: "end_user_send_evm_transaction" });
      const address = resolveEvmAddress(endUser, opts.address);
      return apiClient.sendEvmTransactionWithEndUserAccount(
        endUser.userId,
        { address, transaction: opts.transaction, network: opts.network },
        undefined,
        opts.idempotencyKey,
      );
    },

    async sendEvmAsset(opts: AccountSendEvmAssetOptions): Promise<SendEvmAssetResult> {
      Analytics.trackAction({ action: "end_user_send_evm_asset" });
      const address = resolveEvmAddress(endUser, opts.address);
      const asset = opts.asset ?? "usdc";
      return apiClient.sendEvmAssetWithEndUserAccount(
        endUser.userId,
        address,
        asset,
        {
          to: opts.to,
          amount: opts.amount,
          network: opts.network,
          useCdpPaymaster: opts.useCdpPaymaster,
          paymasterUrl: opts.paymasterUrl,
        },
        undefined,
        opts.idempotencyKey,
      );
    },

    async sendUserOperation(
      opts: AccountSendUserOperationOptions,
    ): Promise<SendUserOperationResult> {
      Analytics.trackAction({ action: "end_user_send_user_operation" });
      const address = resolveEvmSmartAccountAddress(endUser, opts.address);
      return apiClient.sendUserOperationWithEndUserAccount(
        endUser.userId,
        address,
        {
          network: opts.network,
          calls: opts.calls,
          useCdpPaymaster: opts.useCdpPaymaster,
          paymasterUrl: opts.paymasterUrl,
          dataSuffix: opts.dataSuffix,
        },
        undefined,
        opts.idempotencyKey,
      );
    },

    // ─── Delegated EVM EIP-7702 Delegation Method ───

    async createEvmEip7702Delegation(
      opts: AccountCreateEvmEip7702DelegationOptions,
    ): Promise<CreateEvmEip7702DelegationForEndUserResult> {
      Analytics.trackAction({ action: "end_user_create_evm_eip7702_delegation" });
      const address = resolveEvmAddress(endUser, opts.address);
      return apiClient.createEvmEip7702DelegationWithEndUserAccount(
        endUser.userId,
        {
          address,
          network: opts.network,
          enableSpendPermissions: opts.enableSpendPermissions,
        },
        undefined,
        opts.idempotencyKey,
      );
    },

    // ─── Delegated Solana Sign Methods ───

    async signSolanaMessage(
      opts: AccountSignSolanaMessageOptions,
    ): Promise<SignSolanaMessageResult> {
      Analytics.trackAction({ action: "end_user_sign_solana_message" });
      const address = resolveSolanaAddress(endUser, opts.address);
      return apiClient.signSolanaMessageWithEndUserAccount(
        endUser.userId,
        { address, message: opts.message },
        undefined,
        opts.idempotencyKey,
      );
    },

    async signSolanaTransaction(
      opts: AccountSignSolanaTransactionOptions,
    ): Promise<SignSolanaTransactionResult> {
      Analytics.trackAction({ action: "end_user_sign_solana_transaction" });
      const address = resolveSolanaAddress(endUser, opts.address);
      return apiClient.signSolanaTransactionWithEndUserAccount(
        endUser.userId,
        { address, transaction: opts.transaction },
        undefined,
        opts.idempotencyKey,
      );
    },

    // ─── Delegated Solana Send Methods ───

    async sendSolanaTransaction(
      opts: AccountSendSolanaTransactionOptions,
    ): Promise<SendSolanaTransactionResult> {
      Analytics.trackAction({ action: "end_user_send_solana_transaction" });
      const address = resolveSolanaAddress(endUser, opts.address);
      return apiClient.sendSolanaTransactionWithEndUserAccount(
        endUser.userId,
        { address, transaction: opts.transaction, network: opts.network },
        undefined,
        opts.idempotencyKey,
      );
    },

    async sendSolanaAsset(opts: AccountSendSolanaAssetOptions): Promise<SendSolanaAssetResult> {
      Analytics.trackAction({ action: "end_user_send_solana_asset" });
      const address = resolveSolanaAddress(endUser, opts.address);
      const asset = opts.asset ?? "usdc";
      return apiClient.sendSolanaAssetWithEndUserAccount(
        endUser.userId,
        address,
        asset,
        {
          to: opts.to,
          amount: opts.amount,
          network: opts.network,
          createRecipientAta: opts.createRecipientAta,
        },
        undefined,
        opts.idempotencyKey,
      );
    },
  };

  return endUserAccount;
}
