/**
 * @ai-summary Data access layer for subscriptions
 * @ai-context All database operations for subscriptions, events, donations, vehicle selections
 */

import { Pool } from 'pg';
import { logger } from '../../../core/logging/logger';
import {
  Subscription,
  SubscriptionEvent,
  Donation,
  TierVehicleSelection,
  SubscriptionTier,
  CreateSubscriptionRequest,
  UpdateSubscriptionData,
  CreateSubscriptionEventRequest,
  CreateDonationRequest,
  UpdateDonationData,
  CreateTierVehicleSelectionRequest,
} from '../domain/subscriptions.types';

export class SubscriptionsRepository {
  constructor(private pool: Pool) {}

  // ========== Subscriptions ==========

  /**
   * Create a new subscription
   */
  async create(data: CreateSubscriptionRequest & { stripeCustomerId: string }): Promise<Subscription> {
    const query = `
      INSERT INTO subscriptions (
        user_id, stripe_customer_id, tier, billing_cycle
      )
      VALUES ($1, $2, $3, $4)
      RETURNING *
    `;

    const values = [
      data.userId,
      data.stripeCustomerId,
      data.tier,
      data.billingCycle,
    ];

    try {
      const result = await this.pool.query(query, values);
      logger.info('Subscription created', { subscriptionId: result.rows[0].id, userId: data.userId });
      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to create subscription', {
        userId: data.userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find subscription by user ID
   */
  async findByUserId(userId: string): Promise<Subscription | null> {
    const query = `
      SELECT * FROM subscriptions
      WHERE user_id = $1
      ORDER BY created_at DESC
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [userId]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find subscription by user ID', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find subscription by Stripe customer ID
   */
  async findByStripeCustomerId(stripeCustomerId: string): Promise<Subscription | null> {
    const query = `
      SELECT * FROM subscriptions
      WHERE stripe_customer_id = $1
      ORDER BY created_at DESC
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [stripeCustomerId]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find subscription by Stripe customer ID', {
        stripeCustomerId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find subscription by Stripe subscription ID
   */
  async findByStripeSubscriptionId(stripeSubscriptionId: string): Promise<Subscription | null> {
    const query = `
      SELECT * FROM subscriptions
      WHERE stripe_subscription_id = $1
    `;

    try {
      const result = await this.pool.query(query, [stripeSubscriptionId]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find subscription by Stripe subscription ID', {
        stripeSubscriptionId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Update a subscription
   */
  async update(id: string, data: UpdateSubscriptionData): Promise<Subscription | null> {
    const fields = [];
    const values = [];
    let paramCount = 1;

    if (data.stripeSubscriptionId !== undefined) {
      fields.push(`stripe_subscription_id = $${paramCount++}`);
      values.push(data.stripeSubscriptionId);
    }
    if (data.tier !== undefined) {
      fields.push(`tier = $${paramCount++}`);
      values.push(data.tier);
    }
    if (data.billingCycle !== undefined) {
      fields.push(`billing_cycle = $${paramCount++}`);
      values.push(data.billingCycle);
    }
    if (data.status !== undefined) {
      fields.push(`status = $${paramCount++}`);
      values.push(data.status);
    }
    if (data.currentPeriodStart !== undefined) {
      fields.push(`current_period_start = $${paramCount++}`);
      values.push(data.currentPeriodStart);
    }
    if (data.currentPeriodEnd !== undefined) {
      fields.push(`current_period_end = $${paramCount++}`);
      values.push(data.currentPeriodEnd);
    }
    if (data.gracePeriodEnd !== undefined) {
      fields.push(`grace_period_end = $${paramCount++}`);
      values.push(data.gracePeriodEnd);
    }
    if (data.cancelAtPeriodEnd !== undefined) {
      fields.push(`cancel_at_period_end = $${paramCount++}`);
      values.push(data.cancelAtPeriodEnd);
    }

    if (fields.length === 0) {
      logger.warn('No fields to update for subscription', { id });
      return this.findById(id);
    }

    values.push(id);
    const query = `
      UPDATE subscriptions
      SET ${fields.join(', ')}
      WHERE id = $${paramCount}
      RETURNING *
    `;

    try {
      const result = await this.pool.query(query, values);

      if (result.rows.length === 0) {
        return null;
      }

      logger.info('Subscription updated', { subscriptionId: id });
      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to update subscription', {
        subscriptionId: id,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find subscription by ID
   */
  async findById(id: string): Promise<Subscription | null> {
    const query = 'SELECT * FROM subscriptions WHERE id = $1';

    try {
      const result = await this.pool.query(query, [id]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find subscription by ID', {
        subscriptionId: id,
        error: error.message,
      });
      throw error;
    }
  }

  // ========== Subscription Events ==========

  /**
   * Create a subscription event
   */
  async createEvent(data: CreateSubscriptionEventRequest): Promise<SubscriptionEvent> {
    const query = `
      INSERT INTO subscription_events (
        subscription_id, stripe_event_id, event_type, payload
      )
      VALUES ($1, $2, $3, $4)
      RETURNING *
    `;

    const values = [
      data.subscriptionId,
      data.stripeEventId,
      data.eventType,
      JSON.stringify(data.payload),
    ];

    try {
      const result = await this.pool.query(query, values);
      logger.info('Subscription event created', {
        eventId: result.rows[0].id,
        stripeEventId: data.stripeEventId,
        eventType: data.eventType,
      });
      return this.mapEventRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to create subscription event', {
        stripeEventId: data.stripeEventId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find event by Stripe event ID (for idempotency)
   */
  async findEventByStripeId(stripeEventId: string): Promise<SubscriptionEvent | null> {
    const query = `
      SELECT * FROM subscription_events
      WHERE stripe_event_id = $1
    `;

    try {
      const result = await this.pool.query(query, [stripeEventId]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapEventRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find subscription event by Stripe ID', {
        stripeEventId,
        error: error.message,
      });
      throw error;
    }
  }

  // ========== Donations ==========

  /**
   * Create a donation
   */
  async createDonation(data: CreateDonationRequest & { stripePaymentIntentId: string }): Promise<Donation> {
    const query = `
      INSERT INTO donations (
        user_id, stripe_payment_intent_id, amount_cents, currency
      )
      VALUES ($1, $2, $3, $4)
      RETURNING *
    `;

    const values = [
      data.userId,
      data.stripePaymentIntentId,
      data.amountCents,
      data.currency || 'usd',
    ];

    try {
      const result = await this.pool.query(query, values);
      logger.info('Donation created', {
        donationId: result.rows[0].id,
        userId: data.userId,
        amountCents: data.amountCents,
      });
      return this.mapDonationRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to create donation', {
        userId: data.userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find donations by user ID
   */
  async findDonationsByUserId(userId: string): Promise<Donation[]> {
    const query = `
      SELECT * FROM donations
      WHERE user_id = $1
      ORDER BY created_at DESC
    `;

    try {
      const result = await this.pool.query(query, [userId]);
      return result.rows.map(row => this.mapDonationRow(row));
    } catch (error: any) {
      logger.error('Failed to find donations by user ID', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find donation by Stripe payment intent ID
   */
  async findDonationByPaymentIntentId(stripePaymentIntentId: string): Promise<Donation | null> {
    const query = `
      SELECT * FROM donations
      WHERE stripe_payment_intent_id = $1
    `;

    try {
      const result = await this.pool.query(query, [stripePaymentIntentId]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapDonationRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find donation by payment intent ID', {
        stripePaymentIntentId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Update a donation
   */
  async updateDonation(id: string, data: UpdateDonationData): Promise<Donation | null> {
    const fields = [];
    const values = [];
    let paramCount = 1;

    if (data.status !== undefined) {
      fields.push(`status = $${paramCount++}`);
      values.push(data.status);
    }

    if (fields.length === 0) {
      logger.warn('No fields to update for donation', { id });
      return this.findDonationById(id);
    }

    values.push(id);
    const query = `
      UPDATE donations
      SET ${fields.join(', ')}
      WHERE id = $${paramCount}
      RETURNING *
    `;

    try {
      const result = await this.pool.query(query, values);

      if (result.rows.length === 0) {
        return null;
      }

      logger.info('Donation updated', { donationId: id });
      return this.mapDonationRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to update donation', {
        donationId: id,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find donation by ID
   */
  async findDonationById(id: string): Promise<Donation | null> {
    const query = 'SELECT * FROM donations WHERE id = $1';

    try {
      const result = await this.pool.query(query, [id]);

      if (result.rows.length === 0) {
        return null;
      }

      return this.mapDonationRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to find donation by ID', {
        donationId: id,
        error: error.message,
      });
      throw error;
    }
  }

  // ========== Tier Vehicle Selections ==========

  /**
   * Create a tier vehicle selection
   */
  async createVehicleSelection(data: CreateTierVehicleSelectionRequest): Promise<TierVehicleSelection> {
    const query = `
      INSERT INTO tier_vehicle_selections (
        user_id, vehicle_id
      )
      VALUES ($1, $2)
      RETURNING *
    `;

    const values = [data.userId, data.vehicleId];

    try {
      const result = await this.pool.query(query, values);
      logger.info('Tier vehicle selection created', {
        selectionId: result.rows[0].id,
        userId: data.userId,
        vehicleId: data.vehicleId,
      });
      return this.mapVehicleSelectionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to create tier vehicle selection', {
        userId: data.userId,
        vehicleId: data.vehicleId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Find vehicle selections by user ID
   */
  async findVehicleSelectionsByUserId(userId: string): Promise<TierVehicleSelection[]> {
    const query = `
      SELECT * FROM tier_vehicle_selections
      WHERE user_id = $1
      ORDER BY selected_at DESC
    `;

    try {
      const result = await this.pool.query(query, [userId]);
      return result.rows.map(row => this.mapVehicleSelectionRow(row));
    } catch (error: any) {
      logger.error('Failed to find vehicle selections by user ID', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Delete all vehicle selections for a user
   */
  async deleteVehicleSelectionsByUserId(userId: string): Promise<void> {
    const query = `
      DELETE FROM tier_vehicle_selections
      WHERE user_id = $1
    `;

    try {
      await this.pool.query(query, [userId]);
      logger.info('Vehicle selections deleted', { userId });
    } catch (error: any) {
      logger.error('Failed to delete vehicle selections', {
        userId,
        error: error.message,
      });
      throw error;
    }
  }

  // ========== Transaction Support ==========

  /**
   * Update subscription tier by user ID (supports transactions)
   * Used by adminOverrideTier for atomic dual-table updates
   */
  async updateTierByUserId(
    userId: string,
    tier: SubscriptionTier,
    client?: any
  ): Promise<Subscription | null> {
    const queryClient = client || this.pool;
    const query = `
      UPDATE subscriptions
      SET tier = $1
      WHERE user_id = $2
      RETURNING *
    `;

    try {
      const result = await queryClient.query(query, [tier, userId]);

      if (result.rows.length === 0) {
        return null;
      }

      logger.info('Subscription tier updated by user ID', { userId, tier });
      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to update subscription tier by user ID', {
        userId,
        tier,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Create subscription for admin override (when user has no subscription record)
   * Uses provided client for transaction support
   */
  async createForAdminOverride(
    userId: string,
    tier: SubscriptionTier,
    client?: any
  ): Promise<Subscription> {
    const queryClient = client || this.pool;
    // Generate a placeholder Stripe customer ID since admin override bypasses Stripe
    const placeholderCustomerId = `admin_override_${userId}_${Date.now()}`;

    const query = `
      INSERT INTO subscriptions (
        user_id, stripe_customer_id, tier, billing_cycle, status
      )
      VALUES ($1, $2, $3, 'monthly', 'active')
      RETURNING *
    `;

    const values = [userId, placeholderCustomerId, tier];

    try {
      const result = await queryClient.query(query, values);
      logger.info('Subscription created via admin override', {
        subscriptionId: result.rows[0].id,
        userId,
        tier,
      });
      return this.mapSubscriptionRow(result.rows[0]);
    } catch (error: any) {
      logger.error('Failed to create subscription for admin override', {
        userId,
        tier,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Get pool for transaction management
   */
  getPool(): Pool {
    return this.pool;
  }

  // ========== Private Mapping Methods ==========

  private mapSubscriptionRow(row: any): Subscription {
    return {
      id: row.id,
      userId: row.user_id,
      stripeCustomerId: row.stripe_customer_id,
      stripeSubscriptionId: row.stripe_subscription_id || undefined,
      tier: row.tier,
      billingCycle: row.billing_cycle || undefined,
      status: row.status,
      currentPeriodStart: row.current_period_start || undefined,
      currentPeriodEnd: row.current_period_end || undefined,
      gracePeriodEnd: row.grace_period_end || undefined,
      cancelAtPeriodEnd: row.cancel_at_period_end,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }

  private mapEventRow(row: any): SubscriptionEvent {
    return {
      id: row.id,
      subscriptionId: row.subscription_id,
      stripeEventId: row.stripe_event_id,
      eventType: row.event_type,
      payload: typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,
      createdAt: row.created_at,
    };
  }

  private mapDonationRow(row: any): Donation {
    return {
      id: row.id,
      userId: row.user_id,
      stripePaymentIntentId: row.stripe_payment_intent_id,
      amountCents: row.amount_cents,
      currency: row.currency,
      status: row.status,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }

  private mapVehicleSelectionRow(row: any): TierVehicleSelection {
    return {
      id: row.id,
      userId: row.user_id,
      vehicleId: row.vehicle_id,
      selectedAt: row.selected_at,
    };
  }
}
