/**
 * @ai-summary Subscription business logic and webhook handling
 * @ai-context Manages subscription lifecycle, Stripe integration, and tier syncing
 */

import { Pool } from 'pg';
import { logger } from '../../../core/logging/logger';
import { SubscriptionsRepository } from '../data/subscriptions.repository';
import { StripeClient } from '../external/stripe/stripe.client';
import { UserProfileRepository } from '../../user-profile/data/user-profile.repository';
import { NotificationsService } from '../../notifications/domain/notifications.service';
import {
  Subscription,
  SubscriptionResponse,
  SubscriptionTier,
  BillingCycle,
  SubscriptionStatus,
  UpdateSubscriptionData,
  NeedsVehicleSelectionResponse,
} from './subscriptions.types';
import { VehiclesRepository } from '../../vehicles/data/vehicles.repository';

interface StripeWebhookEvent {
  id: string;
  type: string;
  data: {
    object: any;
  };
}

export class SubscriptionsService {
  private userProfileRepository: UserProfileRepository;
  private vehiclesRepository: VehiclesRepository;
  private notificationsService: NotificationsService;

  constructor(
    private repository: SubscriptionsRepository,
    private stripeClient: StripeClient,
    pool: Pool
  ) {
    this.userProfileRepository = new UserProfileRepository(pool);
    this.vehiclesRepository = new VehiclesRepository(pool);
    this.notificationsService = new NotificationsService();
  }

  /**
   * Get current subscription for user
   */
  async getSubscription(userId: string): Promise<SubscriptionResponse | null> {
    try {
      const subscription = await this.repository.findByUserId(userId);

      if (!subscription) {
        return null;
      }

      return this.mapToResponse(subscription);
    } catch (error: any) {
      logger.error('Failed to get subscription', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Check if user needs to make a vehicle selection after auto-downgrade
   * Returns true if: user is on free tier, has >2 vehicles, and hasn't made selections
   */
  async checkNeedsVehicleSelection(userId: string): Promise<NeedsVehicleSelectionResponse> {
    const FREE_TIER_VEHICLE_LIMIT = 2;

    try {
      // Get current subscription
      const subscription = await this.repository.findByUserId(userId);

      // No subscription or not on free tier = no selection needed
      if (!subscription || subscription.tier !== 'free') {
        return {
          needsSelection: false,
          vehicleCount: 0,
          maxAllowed: FREE_TIER_VEHICLE_LIMIT,
        };
      }

      // Count user's active vehicles
      const vehicleCount = await this.vehiclesRepository.countByUserId(userId);

      // If within limit, no selection needed
      if (vehicleCount <= FREE_TIER_VEHICLE_LIMIT) {
        return {
          needsSelection: false,
          vehicleCount,
          maxAllowed: FREE_TIER_VEHICLE_LIMIT,
        };
      }

      // Check if user already has vehicle selections
      const existingSelections = await this.repository.findVehicleSelectionsByUserId(userId);

      // If user already made selections, no selection needed
      if (existingSelections.length > 0) {
        return {
          needsSelection: false,
          vehicleCount,
          maxAllowed: FREE_TIER_VEHICLE_LIMIT,
        };
      }

      // User is on free tier, has >2 vehicles, and hasn't made selections
      return {
        needsSelection: true,
        vehicleCount,
        maxAllowed: FREE_TIER_VEHICLE_LIMIT,
      };
    } catch (error: any) {
      logger.error('Failed to check needs vehicle selection', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Create new subscription (Stripe customer + initial free tier record)
   */
  async createSubscription(userId: string, email: string): Promise<Subscription> {
    try {
      logger.info('Creating subscription', { userId, email });

      // Check if user already has a subscription
      const existing = await this.repository.findByUserId(userId);
      if (existing) {
        logger.warn('User already has a subscription', { userId, subscriptionId: existing.id });
        return existing;
      }

      // Create Stripe customer
      const stripeCustomer = await this.stripeClient.createCustomer(email);

      // Create subscription record with free tier
      const subscription = await this.repository.create({
        userId,
        stripeCustomerId: stripeCustomer.id,
        tier: 'free',
        billingCycle: 'monthly',
      });

      logger.info('Subscription created', {
        subscriptionId: subscription.id,
        userId,
        stripeCustomerId: stripeCustomer.id,
      });

      return subscription;
    } catch (error: any) {
      logger.error('Failed to create subscription', {
        userId,
        email,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Upgrade from current tier to new tier
   */
  async upgradeSubscription(
    userId: string,
    newTier: 'pro' | 'enterprise',
    billingCycle: 'monthly' | 'yearly',
    paymentMethodId: string
  ): Promise<Subscription> {
    try {
      logger.info('Upgrading subscription', { userId, newTier, billingCycle });

      // Get current subscription
      const currentSubscription = await this.repository.findByUserId(userId);
      if (!currentSubscription) {
        throw new Error('No subscription found for user');
      }

      // Determine price ID from environment variables
      const priceId = this.getPriceId(newTier, billingCycle);

      // Create or update Stripe subscription
      const stripeSubscription = await this.stripeClient.createSubscription(
        currentSubscription.stripeCustomerId,
        priceId,
        paymentMethodId
      );

      // Update subscription record
      const updateData: UpdateSubscriptionData = {
        stripeSubscriptionId: stripeSubscription.id,
        tier: newTier,
        billingCycle,
        status: this.mapStripeStatus(stripeSubscription.status),
        currentPeriodStart: new Date(stripeSubscription.currentPeriodStart * 1000),
        currentPeriodEnd: new Date(stripeSubscription.currentPeriodEnd * 1000),
        cancelAtPeriodEnd: false,
      };

      const updatedSubscription = await this.repository.update(
        currentSubscription.id,
        updateData
      );

      if (!updatedSubscription) {
        throw new Error('Failed to update subscription');
      }

      // Sync tier to user profile
      await this.syncTierToUserProfile(userId, newTier);

      // Send tier change notification
      await this.sendTierChangeNotificationSafe(
        userId,
        currentSubscription.tier,
        newTier,
        'user_upgrade'
      );

      logger.info('Subscription upgraded', {
        subscriptionId: updatedSubscription.id,
        userId,
        newTier,
        billingCycle,
      });

      return updatedSubscription;
    } catch (error: any) {
      logger.error('Failed to upgrade subscription', {
        userId,
        newTier,
        billingCycle,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Cancel subscription (schedules for end of period)
   */
  async cancelSubscription(userId: string): Promise<Subscription> {
    try {
      logger.info('Canceling subscription', { userId });

      // Get current subscription
      const currentSubscription = await this.repository.findByUserId(userId);
      if (!currentSubscription) {
        throw new Error('No subscription found for user');
      }

      if (!currentSubscription.stripeSubscriptionId) {
        throw new Error('No active Stripe subscription to cancel');
      }

      // Cancel at period end in Stripe
      await this.stripeClient.cancelSubscription(
        currentSubscription.stripeSubscriptionId,
        true
      );

      // Update subscription record
      const updatedSubscription = await this.repository.update(currentSubscription.id, {
        cancelAtPeriodEnd: true,
      });

      if (!updatedSubscription) {
        throw new Error('Failed to update subscription');
      }

      logger.info('Subscription canceled', {
        subscriptionId: updatedSubscription.id,
        userId,
      });

      return updatedSubscription;
    } catch (error: any) {
      logger.error('Failed to cancel subscription', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Reactivate a pending cancellation
   */
  async reactivateSubscription(userId: string): Promise<Subscription> {
    try {
      logger.info('Reactivating subscription', { userId });

      // Get current subscription
      const currentSubscription = await this.repository.findByUserId(userId);
      if (!currentSubscription) {
        throw new Error('No subscription found for user');
      }

      if (!currentSubscription.stripeSubscriptionId) {
        throw new Error('No active Stripe subscription to reactivate');
      }

      if (!currentSubscription.cancelAtPeriodEnd) {
        logger.warn('Subscription is not pending cancellation', {
          subscriptionId: currentSubscription.id,
          userId,
        });
        return currentSubscription;
      }

      // Reactivate in Stripe (remove cancel_at_period_end flag)
      await this.stripeClient.cancelSubscription(
        currentSubscription.stripeSubscriptionId,
        false
      );

      // Update subscription record
      const updatedSubscription = await this.repository.update(currentSubscription.id, {
        cancelAtPeriodEnd: false,
      });

      if (!updatedSubscription) {
        throw new Error('Failed to update subscription');
      }

      logger.info('Subscription reactivated', {
        subscriptionId: updatedSubscription.id,
        userId,
      });

      return updatedSubscription;
    } catch (error: any) {
      logger.error('Failed to reactivate subscription', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Downgrade subscription to a lower tier with vehicle selection
   */
  async downgradeSubscription(
    userId: string,
    targetTier: SubscriptionTier,
    vehicleIdsToKeep: string[]
  ): Promise<Subscription> {
    try {
      logger.info('Downgrading subscription', { userId, targetTier, vehicleCount: vehicleIdsToKeep.length });

      // Get current subscription
      const currentSubscription = await this.repository.findByUserId(userId);
      if (!currentSubscription) {
        throw new Error('No subscription found for user');
      }

      // Define tier limits
      const tierLimits: Record<SubscriptionTier, number | null> = {
        free: 2,
        pro: 5,
        enterprise: null, // unlimited
      };

      const targetLimit = tierLimits[targetTier];

      // Validate vehicle selection count
      if (targetLimit !== null && vehicleIdsToKeep.length > targetLimit) {
        throw new Error(`Vehicle selection exceeds tier limit. ${targetTier} tier allows ${targetLimit} vehicles, but ${vehicleIdsToKeep.length} were selected.`);
      }

      // Cancel current Stripe subscription if exists (downgrading from paid tier)
      if (currentSubscription.stripeSubscriptionId) {
        await this.stripeClient.cancelSubscription(
          currentSubscription.stripeSubscriptionId,
          false // Cancel immediately, not at period end
        );
      }

      // Clear previous vehicle selections
      await this.repository.deleteVehicleSelectionsByUserId(userId);

      // Save new vehicle selections
      for (const vehicleId of vehicleIdsToKeep) {
        await this.repository.createVehicleSelection({
          userId,
          vehicleId,
        });
      }

      // Update subscription tier
      const updateData: UpdateSubscriptionData = {
        tier: targetTier,
        status: 'active',
        stripeSubscriptionId: undefined,
        billingCycle: undefined,
        cancelAtPeriodEnd: false,
      };

      const updatedSubscription = await this.repository.update(
        currentSubscription.id,
        updateData
      );

      if (!updatedSubscription) {
        throw new Error('Failed to update subscription');
      }

      // Sync tier to user profile
      await this.syncTierToUserProfile(userId, targetTier);

      // Send tier change notification
      await this.sendTierChangeNotificationSafe(
        userId,
        currentSubscription.tier,
        targetTier,
        'user_downgrade'
      );

      logger.info('Subscription downgraded', {
        subscriptionId: updatedSubscription.id,
        userId,
        targetTier,
        vehicleCount: vehicleIdsToKeep.length,
      });

      return updatedSubscription;
    } catch (error: any) {
      logger.error('Failed to downgrade subscription', {
        userId,
        targetTier,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Handle incoming Stripe webhook event
   */
  async handleWebhookEvent(payload: Buffer, signature: string): Promise<void> {
    try {
      // Construct and verify webhook event
      const event = this.stripeClient.constructWebhookEvent(
        payload,
        signature
      ) as StripeWebhookEvent;

      logger.info('Processing webhook event', {
        eventId: event.id,
        eventType: event.type,
      });

      // Check idempotency - skip if we've already processed this event
      const existingEvent = await this.repository.findEventByStripeId(event.id);
      if (existingEvent) {
        logger.info('Event already processed, skipping', { eventId: event.id });
        return;
      }

      // Process based on event type
      switch (event.type) {
        case 'customer.subscription.created':
          await this.handleSubscriptionCreated(event);
          break;
        case 'customer.subscription.updated':
          await this.handleSubscriptionUpdated(event);
          break;
        case 'customer.subscription.deleted':
          await this.handleSubscriptionDeleted(event);
          break;
        case 'invoice.payment_succeeded':
          await this.handlePaymentSucceeded(event);
          break;
        case 'invoice.payment_failed':
          await this.handlePaymentFailed(event);
          break;
        case 'payment_intent.succeeded':
          await this.handleDonationPaymentSucceeded(event);
          break;
        default:
          logger.info('Unhandled webhook event type', { eventType: event.type });
      }

      logger.info('Webhook event processed', { eventId: event.id, eventType: event.type });
    } catch (error: any) {
      logger.error('Failed to handle webhook event', {
        error: error.message,
      });
      throw error;
    }
  }

  // ========== Private Helper Methods ==========

  /**
   * Handle customer.subscription.created webhook
   */
  private async handleSubscriptionCreated(event: StripeWebhookEvent): Promise<void> {
    const stripeSubscription = event.data.object;

    // Find subscription by Stripe customer ID
    const subscription = await this.repository.findByStripeCustomerId(
      stripeSubscription.customer
    );

    if (!subscription) {
      logger.warn('Subscription not found for Stripe customer', {
        stripeCustomerId: stripeSubscription.customer,
      });
      return;
    }

    // Update subscription with Stripe subscription ID
    await this.repository.update(subscription.id, {
      stripeSubscriptionId: stripeSubscription.id,
      status: this.mapStripeStatus(stripeSubscription.status),
      currentPeriodStart: new Date(stripeSubscription.current_period_start * 1000),
      currentPeriodEnd: new Date(stripeSubscription.current_period_end * 1000),
    });

    // Log event
    await this.repository.createEvent({
      subscriptionId: subscription.id,
      stripeEventId: event.id,
      eventType: event.type,
      payload: event.data.object,
    });
  }

  /**
   * Handle customer.subscription.updated webhook
   */
  private async handleSubscriptionUpdated(event: StripeWebhookEvent): Promise<void> {
    const stripeSubscription = event.data.object;

    // Find subscription by Stripe subscription ID
    const subscription = await this.repository.findByStripeSubscriptionId(
      stripeSubscription.id
    );

    if (!subscription) {
      logger.warn('Subscription not found for Stripe subscription', {
        stripeSubscriptionId: stripeSubscription.id,
      });
      return;
    }

    // Determine tier from price metadata or plan
    const tier = this.determineTierFromStripeSubscription(stripeSubscription);

    // Update subscription
    const updateData: UpdateSubscriptionData = {
      status: this.mapStripeStatus(stripeSubscription.status),
      tier,
      currentPeriodStart: new Date(stripeSubscription.current_period_start * 1000),
      currentPeriodEnd: new Date(stripeSubscription.current_period_end * 1000),
      cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end || false,
    };

    await this.repository.update(subscription.id, updateData);

    // Sync tier to user profile
    await this.syncTierToUserProfile(subscription.userId, tier);

    // Log event
    await this.repository.createEvent({
      subscriptionId: subscription.id,
      stripeEventId: event.id,
      eventType: event.type,
      payload: event.data.object,
    });
  }

  /**
   * Handle customer.subscription.deleted webhook
   */
  private async handleSubscriptionDeleted(event: StripeWebhookEvent): Promise<void> {
    const stripeSubscription = event.data.object;

    // Find subscription by Stripe subscription ID
    const subscription = await this.repository.findByStripeSubscriptionId(
      stripeSubscription.id
    );

    if (!subscription) {
      logger.warn('Subscription not found for Stripe subscription', {
        stripeSubscriptionId: stripeSubscription.id,
      });
      return;
    }

    // Update subscription to canceled
    await this.repository.update(subscription.id, {
      status: 'canceled',
    });

    // Downgrade tier to free
    await this.syncTierToUserProfile(subscription.userId, 'free');

    // Log event
    await this.repository.createEvent({
      subscriptionId: subscription.id,
      stripeEventId: event.id,
      eventType: event.type,
      payload: event.data.object,
    });
  }

  /**
   * Handle invoice.payment_succeeded webhook
   */
  private async handlePaymentSucceeded(event: StripeWebhookEvent): Promise<void> {
    const invoice = event.data.object;

    // Find subscription by Stripe subscription ID
    const subscription = await this.repository.findByStripeSubscriptionId(
      invoice.subscription
    );

    if (!subscription) {
      logger.warn('Subscription not found for Stripe subscription', {
        stripeSubscriptionId: invoice.subscription,
      });
      return;
    }

    // Clear grace period and mark as active
    await this.repository.update(subscription.id, {
      status: 'active',
      gracePeriodEnd: undefined,
    });

    // Log event
    await this.repository.createEvent({
      subscriptionId: subscription.id,
      stripeEventId: event.id,
      eventType: event.type,
      payload: event.data.object,
    });
  }

  /**
   * Handle invoice.payment_failed webhook
   */
  private async handlePaymentFailed(event: StripeWebhookEvent): Promise<void> {
    const invoice = event.data.object;

    // Find subscription by Stripe subscription ID
    const subscription = await this.repository.findByStripeSubscriptionId(
      invoice.subscription
    );

    if (!subscription) {
      logger.warn('Subscription not found for Stripe subscription', {
        stripeSubscriptionId: invoice.subscription,
      });
      return;
    }

    // Set grace period (30 days from now)
    const gracePeriodEnd = new Date();
    gracePeriodEnd.setDate(gracePeriodEnd.getDate() + 30);

    await this.repository.update(subscription.id, {
      status: 'past_due',
      gracePeriodEnd,
    });

    // Log event
    await this.repository.createEvent({
      subscriptionId: subscription.id,
      stripeEventId: event.id,
      eventType: event.type,
      payload: event.data.object,
    });
  }

  /**
   * Handle payment_intent.succeeded webhook for donations
   */
  private async handleDonationPaymentSucceeded(event: StripeWebhookEvent): Promise<void> {
    const paymentIntent = event.data.object;

    // Check if this is a donation (based on metadata)
    if (paymentIntent.metadata?.type !== 'donation') {
      logger.info('PaymentIntent is not a donation, skipping', {
        paymentIntentId: paymentIntent.id,
      });
      return;
    }

    // Find donation by payment intent ID
    const donation = await this.repository.findDonationByPaymentIntentId(
      paymentIntent.id
    );

    if (!donation) {
      logger.warn('Donation not found for payment intent', {
        paymentIntentId: paymentIntent.id,
      });
      return;
    }

    // Update donation status to succeeded
    await this.repository.updateDonation(donation.id, {
      status: 'succeeded',
    });

    logger.info('Donation marked as succeeded via webhook', {
      donationId: donation.id,
      paymentIntentId: paymentIntent.id,
    });
  }

  /**
   * Send tier change notification safely (non-blocking, logs errors)
   */
  private async sendTierChangeNotificationSafe(
    userId: string,
    previousTier: SubscriptionTier,
    newTier: SubscriptionTier,
    reason: 'admin_override' | 'user_upgrade' | 'user_downgrade' | 'grace_period_expiration'
  ): Promise<void> {
    try {
      // Get user profile for email and name
      const userProfile = await this.userProfileRepository.getByAuth0Sub(userId);
      if (!userProfile) {
        logger.warn('User profile not found for tier change notification', { userId });
        return;
      }

      const userEmail = userProfile.notificationEmail || userProfile.email;
      const userName = userProfile.displayName || userProfile.email.split('@')[0];

      await this.notificationsService.sendTierChangeNotification(
        userId,
        userEmail,
        userName,
        previousTier,
        newTier,
        reason
      );

      logger.info('Tier change notification sent', { userId, previousTier, newTier, reason });
    } catch (error: any) {
      // Log but don't throw - notifications are non-critical
      logger.error('Failed to send tier change notification', {
        userId,
        previousTier,
        newTier,
        reason,
        error: error.message,
      });
    }
  }

  /**
   * Sync subscription tier to user_profiles table
   */
  private async syncTierToUserProfile(userId: string, tier: SubscriptionTier): Promise<void> {
    try {
      await this.userProfileRepository.updateSubscriptionTier(userId, tier);
      logger.info('Subscription tier synced to user profile', { userId, tier });
    } catch (error: any) {
      logger.error('Failed to sync tier to user profile', {
        userId,
        tier,
        error: error.message,
      });
      // Don't throw - we don't want to fail the subscription operation if sync fails
    }
  }

  /**
   * Get Stripe price ID from environment variables
   */
  private getPriceId(tier: 'pro' | 'enterprise', billingCycle: BillingCycle): string {
    const envVarMap: Record<string, string> = {
      'pro-monthly': 'STRIPE_PRO_MONTHLY_PRICE_ID',
      'pro-yearly': 'STRIPE_PRO_YEARLY_PRICE_ID',
      'enterprise-monthly': 'STRIPE_ENTERPRISE_MONTHLY_PRICE_ID',
      'enterprise-yearly': 'STRIPE_ENTERPRISE_YEARLY_PRICE_ID',
    };

    const envVar = envVarMap[`${tier}-${billingCycle}`];
    const priceId = process.env[envVar];

    if (!priceId) {
      throw new Error(`Missing environment variable: ${envVar}`);
    }

    return priceId;
  }

  /**
   * Map Stripe subscription status to our status type
   */
  private mapStripeStatus(stripeStatus: string): SubscriptionStatus {
    switch (stripeStatus) {
      case 'active':
      case 'trialing':
        return 'active';
      case 'past_due':
        return 'past_due';
      case 'canceled':
      case 'incomplete_expired':
        return 'canceled';
      case 'unpaid':
        return 'unpaid';
      default:
        logger.warn('Unknown Stripe status, defaulting to canceled', { stripeStatus });
        return 'canceled';
    }
  }

  /**
   * Determine tier from Stripe subscription object
   */
  private determineTierFromStripeSubscription(stripeSubscription: any): SubscriptionTier {
    // Try to extract tier from price metadata or plan
    const priceId = stripeSubscription.items?.data?.[0]?.price?.id;

    if (!priceId) {
      logger.warn('Could not determine tier from Stripe subscription, defaulting to free');
      return 'free';
    }

    // Check environment variables to match price ID to tier
    if (
      priceId === process.env.STRIPE_PRO_MONTHLY_PRICE_ID ||
      priceId === process.env.STRIPE_PRO_YEARLY_PRICE_ID
    ) {
      return 'pro';
    }

    if (
      priceId === process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID ||
      priceId === process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID
    ) {
      return 'enterprise';
    }

    logger.warn('Unknown price ID, defaulting to free', { priceId });
    return 'free';
  }

  /**
   * Admin override of subscription tier (bypasses Stripe)
   * Updates both subscriptions.tier and user_profiles.subscription_tier atomically
   * Creates subscription record if user doesn't have one
   *
   * Note: Admin feature depends on Subscriptions for tier management
   * This is intentional - admin has oversight capabilities
   */
  async adminOverrideTier(userId: string, newTier: SubscriptionTier): Promise<Subscription> {
    const pool = this.repository.getPool();
    const client = await pool.connect();

    try {
      await client.query('BEGIN');

      logger.info('Admin overriding subscription tier', { userId, newTier });

      // Check if user has a subscription record
      const existingSubscription = await this.repository.findByUserId(userId);
      const previousTier: SubscriptionTier = existingSubscription?.tier || 'free';

      let subscription: Subscription;
      if (!existingSubscription) {
        // Create subscription record for user (admin override bypasses Stripe)
        logger.info('Creating subscription record for admin override', { userId, newTier });
        subscription = await this.repository.createForAdminOverride(userId, newTier, client);
      } else {
        // Update existing subscription tier
        const updated = await this.repository.updateTierByUserId(userId, newTier, client);
        if (!updated) {
          throw new Error('Failed to update subscription tier');
        }
        subscription = updated;
      }

      // Sync tier to user_profiles table (within same transaction)
      await client.query(
        'UPDATE user_profiles SET subscription_tier = $1 WHERE auth0_sub = $2',
        [newTier, userId]
      );

      await client.query('COMMIT');

      // Send tier change notification (after transaction committed)
      await this.sendTierChangeNotificationSafe(
        userId,
        previousTier,
        newTier,
        'admin_override'
      );

      logger.info('Admin subscription tier override complete', {
        userId,
        newTier,
        subscriptionId: subscription.id,
      });

      return subscription;
    } catch (error: any) {
      await client.query('ROLLBACK');
      logger.error('Failed to admin override subscription tier', {
        userId,
        newTier,
        error: error.message,
      });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Get invoices for a user's subscription
   */
  async getInvoices(userId: string): Promise<any[]> {
    try {
      const subscription = await this.repository.findByUserId(userId);
      if (!subscription?.stripeCustomerId) {
        return [];
      }
      return this.stripeClient.listInvoices(subscription.stripeCustomerId);
    } catch (error: any) {
      logger.error('Failed to get invoices', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Map subscription entity to response DTO
   */
  private mapToResponse(subscription: Subscription): SubscriptionResponse {
    return {
      id: subscription.id,
      userId: subscription.userId,
      stripeCustomerId: subscription.stripeCustomerId,
      stripeSubscriptionId: subscription.stripeSubscriptionId,
      tier: subscription.tier,
      billingCycle: subscription.billingCycle,
      status: subscription.status,
      currentPeriodStart: subscription.currentPeriodStart?.toISOString(),
      currentPeriodEnd: subscription.currentPeriodEnd?.toISOString(),
      gracePeriodEnd: subscription.gracePeriodEnd?.toISOString(),
      cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
      createdAt: subscription.createdAt.toISOString(),
      updatedAt: subscription.updatedAt.toISOString(),
    };
  }
}
