user/memberships.ts

/**
 * @module UserMemberships
 */
import './types.js'
import { HttpClient } from '../../infrastructure/http/HttpClient'
import { globalConfig } from '../config'

const baseUrl = `/api/user-memberships`

/**
 * Represents a user membership object
 */
export interface Membership {
  id: number
  user_id: number
  membership_type: string
  start_date: string
  expiration_date: string | null
  status: string
  created_at: string
  updated_at: string
  [key: string]: any
}

/**
 * Represents tokens for Shopify and Recharge integration
 */
export interface RechargeTokens {
  shopify_customer_access_token: string
  store_identifier: string
  recharge_storefront_access_token: string
  storefront_access_token: string
}

/**
 * Represents the response from subscription upgrade
 */
export interface UpgradeSubscriptionResponse {
  action: 'instant_upgrade' | 'shopify'
  message?: string
  url?: string
}

/**
 * Represents the response when user should create an account (no entitlements or user not found)
 */
export interface RestorePurchasesCreateAccountResponse {
  shouldCreateAccount: true
  originalAppUserId?: string
}

/**
 * Represents the response when user should login
 */
export interface RestorePurchasesShouldLoginResponse {
  shouldLogin: true
  email: string
}

/**
 * Represents the response when user is authenticated successfully
 */
export interface RestorePurchasesSuccessResponse {
  success: boolean
  token: string
  tokenType: string
  userId: number
}

/**
 * Represents the response when user should setup an account (entitlements found but account requires setup)
 */
export interface RestorePurchasesSetupAccountResponse {
  shouldSetupAccount: true
  email: string
  originalAppUserId: string
}

/**
 * Represents response for latest subscription platform as best we can determine.
 */
export interface SubscriptionPlatform {
  last_platform: 'ios' | 'android' | 'web' | null
  has_active_platform_subscription: boolean
}

/**
 * Represents all possible responses from RevenueCat purchase restoration
 */
export type RestorePurchasesResponse =
  | RestorePurchasesCreateAccountResponse
  | RestorePurchasesShouldLoginResponse
  | RestorePurchasesSuccessResponse
  | RestorePurchasesSetupAccountResponse

/**
 * Fetches the authenticated user's memberships from the API.
 *
 * @returns {Promise<Array<Membership>>} - A promise that resolves to an array of membership objects.
 *
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * fetchMemberships()
 *   .then(memberships => console.log(memberships))
 *   .catch(error => console.error(error));
 */
export async function fetchMemberships(): Promise<Membership[]> {
  const httpClient = new HttpClient(globalConfig.baseUrl)
  return httpClient.get<Membership[]>(`${baseUrl}/v1`)
}

/**
 * Fetches tokens required for interacting with Shopify and Recharge.
 *
 * @returns {Promise<RechargeTokens>} - A promise that resolves to an object containing:
 *  - {string} shopify_customer_access_token - Shopify customer access token.
 *  - {string} store_identifier - The store domain identifier.
 *  - {string} recharge_storefront_access_token - Recharge storefront access token.
 *  - {string} storefront_access_token - Shopify storefront access token.
 *
 * @throws {Error} - Throws an error if the request fails.
 *
 * @example
 * fetchRechargeTokens()
 *   .then(tokens => console.log(tokens))
 *   .catch(error => console.error(error));
 */
export async function fetchRechargeTokens(): Promise<RechargeTokens> {
  const httpClient = new HttpClient(globalConfig.baseUrl)
  return httpClient.get<RechargeTokens>(`${baseUrl}/v1/subscriptions-tokens`)
}

/**
 * Upgrades the user's subscription or provides a prefilled add-to-cart URL.
 *
 * @returns {Promise<UpgradeSubscriptionResponse>} A promise that resolves to an object containing either:
 *  - {string} action - The action performed (e.g., 'instant_upgrade').
 *  - {string} message - Success message if the subscription was upgraded immediately.
 *  OR
 *  - {string} action - The action performed (e.g., 'shopify').
 *  - {string} url - URL to the ecommerce store with prefilled add-to-cart parameters.
 *
 * @throws {Error} Throws an error if the request fails.
 *
 * @example
 * upgradeSubscription()
 *   .then(response => console.log(response))
 *   .catch(error => console.error(error));
 */
export async function upgradeSubscription(): Promise<UpgradeSubscriptionResponse> {
  const httpClient = new HttpClient(globalConfig.baseUrl)
  return httpClient.get<UpgradeSubscriptionResponse>(`${baseUrl}/v1/update-subscription`)
}

/**
 * Restores purchases by verifying subscriber status with RevenueCat.
 *
 * This function verifies the subscriber's status with RevenueCat and attempts to sync their
 * subscription data with the platform. The backend will search for a user by email (if provided)
 * or by the RevenueCat original app user ID.
 *
 * @param {string} originalAppUserId - The original app user ID from RevenueCat.
 * @param {string} [email] - (Optional) The user's email address. If provided and a user with this
 *                           email exists, their subscription will be synced. If omitted, the backend
 *                           will only search by the RevenueCat original app user ID.
 *
 * @returns {Promise<RestorePurchasesResponse>} - A promise that resolves to one of three possible responses:
 *
 * **Case 1: Should Create Account** (No active entitlements OR user not found with active entitlements)
 *  - {boolean} shouldCreateAccount - Always true
 *  - {string} [originalAppUserId] - RevenueCat ID to link when creating account (only if user has entitlements)
 *
 * **Case 2: Should Login** (User exists but not currently authenticated)
 *  - {boolean} shouldLogin - Always true
 *  - {string} email - The email address of the found user
 *
 * **Case 3: Success** (User authenticated and synced successfully)
 *  - {boolean} success - Always true
 *  - {string} token - Authentication token
 *  - {string} tokenType - Token type 'bearer'
 *  - {number} userId - The user's ID
 *
 * @throws {Error} - Throws an error if the request fails or if required parameters are missing.
 *
 * @example
 * // With email
 * restorePurchases('rc_user_123', 'user@example.com')
 *   .then(response => {
 *     if ('shouldCreateAccount' in response) {
 *       // Handle account creation
 *     } else if ('shouldLogin' in response) {
 *       // Handle login with response.email
 *     } else if ('success' in response) {
 *       // Handle successful authentication with response.token
 *     }
 *   })
 *   .catch(error => console.error(error));
 *
 * @example
 * // Without email (search by original app user ID only)
 * restorePurchases('rc_user_123')
 *   .then(response => console.log(response))
 *   .catch(error => console.error(error));
 */
export async function restorePurchases(
  originalAppUserId: string,
  email?: string|null
): Promise<RestorePurchasesResponse> {
  if (!originalAppUserId) {
    throw new Error('originalAppUserId is a required parameter')
  }

  const requestBody: { original_app_user_id: string; email?: string } = {
    original_app_user_id: originalAppUserId
  }

  // Only include email if it has a valid value
  if (email) {
    requestBody.email = email
  }

  const httpClient = new HttpClient(globalConfig.baseUrl)
  return httpClient.post<RestorePurchasesResponse>(
    `${baseUrl}/v1/revenuecat/restore`,
    requestBody
  )
}

/**
 * Get the upgrade price from Basic to Plus membership.
 * Returns the price based on the user's subscription interval.
 *
 * For monthly subscribers: Returns the monthly upgrade cost (difference between Plus and Base monthly prices, ~$5/month)
 * For yearly subscribers: Returns the monthly equivalent upgrade cost ($3.33/month from $40/year)
 * For lifetime subscribers: Returns the annual upgrade cost for songs add-on ($40/year)
 * If interval cannot be determined: Defaults to monthly price
 *
 * @returns {Promise<{price: number, currency: string, period: string|null}>} - The upgrade price information
 * @property {number} price - The upgrade cost in USD (monthly for month/year, annual for lifetime)
 * @property {string} currency - The currency
 * @property {string|null} period - The billing period for the price ('month' or 'year'). Note: lifetime subscribers return 'year' period with annual price
 *
 * @example
 * getUpgradePrice()
 *   .then(info => {
 *     console.log(`Upgrade price: $${info.price} per ${info.period}`)
 *     // Example outputs:
 *     // Monthly: "Upgrade price: $5 per month"
 *     // Yearly: "Upgrade price: $3.33 per month"
 *     // Lifetime: "Upgrade price: $40 per year"
 *   })
 *   .catch(error => {
 *     console.error('Failed to fetch upgrade price:', error)
 *   })
 */
export async function getUpgradePrice() {
  const httpClient = new HttpClient(globalConfig.baseUrl)
  return  httpClient.get(`${baseUrl}/v1/upgrade-price`)
}

/**
 * @returns {Promise<'ios' | 'android' | 'web' | null>} The platform of the user's last known subscription
 */
export async function fetchLastSubscriptionPlatform(): Promise<'ios' | 'android' | 'web' | null> {
  const httpClient = new HttpClient(globalConfig.baseUrl)
  const response = await httpClient.get<SubscriptionPlatform>(`${baseUrl}/v1/subscription-platform`)
  return response.last_platform
}

/**
* @returns {Promise<boolean>} Whether the user has any subscription from a known platform (web, ios, android)
*/
export async function fetchHasActivePlatformSubscription(): Promise<boolean> {
  const httpClient = new HttpClient(globalConfig.baseUrl)
  const response = await httpClient.get<SubscriptionPlatform>(`${baseUrl}/v1/subscription-platform`)
  return response.has_active_platform_subscription
}