/**
 * Stripe Client Module
 *
 * Handles Stripe client initialization and organization-specific clients.
 * Supports lazy initialization and caching for organization-specific keys.
 */

import Stripe from 'stripe';
import { prisma } from '@/lib/db';
import { decrypt } from '@/lib/encryption';
import { logger } from '@/lib/logger';

// =============================================================================
// Public Key Export
// =============================================================================

export const stripePublicKey = process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY;

// =============================================================================
// Client Caching
// =============================================================================

/** Cache for organization-specific Stripe clients with 5-minute TTL */
const MAX_ORG_STRIPE_CACHE_SIZE = 10000;
const orgStripeClients = new Map<string, { client: Stripe; createdAt: number }>();
const STRIPE_CLIENT_TTL_MS = 5 * 60 * 1000;

/**
 * Add to org Stripe client cache with LRU eviction
 */
function addToOrgStripeCache(
  organizationId: string,
  entry: { client: Stripe; createdAt: number }
): void {
  // Evict oldest entry if at capacity
  if (orgStripeClients.size >= MAX_ORG_STRIPE_CACHE_SIZE) {
    const oldestKey = orgStripeClients.keys().next().value;
    if (oldestKey) {
      orgStripeClients.delete(oldestKey);
    }
  }
  orgStripeClients.set(organizationId, entry);
}

/** Singleton instance for platform Stripe client */
let stripeInstance: Stripe | null = null;

// =============================================================================
// Platform Client
// =============================================================================

/**
 * Get the platform Stripe client (lazy initialization)
 * @throws Error if STRIPE_SECRET_KEY is not set
 */
export function getStripeClient(): Stripe {
  if (!stripeInstance) {
    const secretKey = process.env.STRIPE_SECRET_KEY;

    if (!secretKey) {
      throw new Error('STRIPE_SECRET_KEY is not set in environment variables');
    }

    stripeInstance = new Stripe(secretKey, {
      apiVersion: '2025-12-15.clover',
      typescript: true,
    });
  }

  return stripeInstance;
}

/**
 * Stripe proxy instance that initializes on first use
 * Allows importing stripe without immediate initialization
 */
export const stripe = new Proxy({} as Stripe, {
  get: (_target, prop) => {
    const client = getStripeClient();
    return client[prop as keyof Stripe];
  },
});

// Warn if public key is missing in production
if (!stripePublicKey && process.env.NODE_ENV === 'production') {
  logger.warn('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is not set - Stripe checkout will not work');
}

// =============================================================================
// Organization-Specific Client
// =============================================================================

export interface OrgStripeClientResult {
  client: Stripe;
  isCompanyKeys: boolean;
  mode: 'test' | 'live' | 'platform';
  publishableKey: string | null;
}

/**
 * Get a Stripe client for a specific organization
 * Uses company-specific keys if configured, falls back to platform keys
 *
 * @param organizationId - The organization ID to get client for
 * @returns Object with client, whether it's using company keys, and mode
 */
export async function getStripeClientForOrganization(
  organizationId: string
): Promise<OrgStripeClientResult> {
  // Check cache first
  const cached = orgStripeClients.get(organizationId);
  if (cached && Date.now() - cached.createdAt < STRIPE_CLIENT_TTL_MS) {
    // Get mode from org for cached client
    const org = await prisma.organizations.findUnique({
      where: { id: organizationId },
      select: { stripeKeysMode: true, stripePublishableKey: true },
    });
    return {
      client: cached.client,
      isCompanyKeys: true,
      mode: (org?.stripeKeysMode as 'test' | 'live') || 'test',
      publishableKey: org?.stripePublishableKey || null,
    };
  }

  // Fetch organization's Stripe keys
  const organization = await prisma.organizations.findUnique({
    where: { id: organizationId },
    select: {
      stripeSecretKeyEncrypted: true,
      stripePublishableKey: true,
      stripeKeysMode: true,
    },
  });

  // If organization has configured keys, use them
  if (organization?.stripeSecretKeyEncrypted) {
    try {
      const secretKey = decrypt(organization.stripeSecretKeyEncrypted);
      const client = new Stripe(secretKey, {
        apiVersion: '2025-12-15.clover',
        typescript: true,
      });

      // Cache the client with LRU eviction
      addToOrgStripeCache(organizationId, {
        client,
        createdAt: Date.now(),
      });

      return {
        client,
        isCompanyKeys: true,
        mode: (organization.stripeKeysMode as 'test' | 'live') || 'test',
        publishableKey: organization.stripePublishableKey,
      };
    } catch (error) {
      logger.error('Failed to decrypt organization Stripe keys', error);
      // Fall through to platform keys
    }
  }

  // Fall back to platform keys
  return {
    client: getStripeClient(),
    isCompanyKeys: false,
    mode: 'platform',
    publishableKey: stripePublicKey || null,
  };
}

/**
 * Check if organization has Stripe keys configured
 * @param organizationId - The organization ID to check
 * @returns true if keys are configured, false otherwise
 */
export async function hasOrganizationStripeKeys(organizationId: string): Promise<boolean> {
  const organization = await prisma.organizations.findUnique({
    where: { id: organizationId },
    select: { stripeSecretKeyEncrypted: true },
  });
  return !!organization?.stripeSecretKeyEncrypted;
}

/**
 * Clear cached Stripe client for an organization
 * Call this when keys are updated or deleted
 * @param organizationId - The organization ID to clear cache for
 */
export function clearOrgStripeClientCache(organizationId: string): void {
  orgStripeClients.delete(organizationId);
}
