/**
 * Stripe Service
 * Handles Stripe API integration
 */

import Stripe from 'stripe';
import { db } from '../../../shared/database';
import { logger } from '../../../shared/utils/logger';
import { config } from '../../../config';
import type {
  StripeCustomer,
  CheckoutSession,
  CreateCheckoutSessionInput,
  BillingPortalSession,
  SubscriptionPlan,
} from '../types/payments.types';

// ============================================================================
// Stripe Client
// ============================================================================

const stripe = new Stripe(config.stripe?.secretKey || process.env.STRIPE_SECRET_KEY || '', {
  apiVersion: '2023-10-16',
});

// ============================================================================
// Helper Functions
// ============================================================================

function transformPlan(row: Record<string, unknown>): SubscriptionPlan {
  return {
    id: row.id as string,
    name: row.name as string,
    slug: row.slug as string,
    description: row.description as string | undefined,
    priceMonthly: parseFloat(row.price_monthly as string),
    priceYearly: row.price_yearly ? parseFloat(row.price_yearly as string) : undefined,
    currency: row.currency as string,
    stripePriceIdMonthly: row.stripe_price_id_monthly as string | undefined,
    stripePriceIdYearly: row.stripe_price_id_yearly as string | undefined,
    stripeProductId: row.stripe_product_id as string | undefined,
    features: (row.features as SubscriptionPlan['features']) || [],
    maxWatchlists: row.max_watchlists as number | undefined,
    maxAlerts: row.max_alerts as number | undefined,
    mlPredictionsAccess: row.ml_predictions_access as boolean,
    signalsAccess: row.signals_access as boolean,
    backtestingAccess: row.backtesting_access as boolean,
    apiAccess: row.api_access as boolean,
    prioritySupport: row.priority_support as boolean,
    coursesAccess: row.courses_access as SubscriptionPlan['coursesAccess'],
    sortOrder: row.sort_order as number,
    isFeatured: row.is_featured as boolean,
    isActive: row.is_active as boolean,
    createdAt: new Date(row.created_at as string),
    updatedAt: new Date(row.updated_at as string),
  };
}

// ============================================================================
// Stripe Service Class
// ============================================================================

class StripeService {
  // ==========================================================================
  // Customer Management
  // ==========================================================================

  async getOrCreateCustomer(userId: string, email: string): Promise<StripeCustomer> {
    // Check if customer exists
    const existing = await db.query<Record<string, unknown>>(
      `SELECT * FROM financial.stripe_customers WHERE user_id = $1`,
      [userId]
    );

    if (existing.rows.length > 0) {
      const row = existing.rows[0];
      return {
        id: row.id as string,
        userId: row.user_id as string,
        stripeCustomerId: row.stripe_customer_id as string,
        email: row.email as string | undefined,
        defaultPaymentMethodId: row.default_payment_method_id as string | undefined,
        metadata: row.metadata as Record<string, unknown> | undefined,
        createdAt: new Date(row.created_at as string),
        updatedAt: new Date(row.updated_at as string),
      };
    }

    // Create Stripe customer
    const stripeCustomer = await stripe.customers.create({
      email,
      metadata: { userId },
    });

    // Save to database
    const result = await db.query<Record<string, unknown>>(
      `INSERT INTO financial.stripe_customers (user_id, stripe_customer_id, email)
       VALUES ($1, $2, $3)
       RETURNING *`,
      [userId, stripeCustomer.id, email]
    );

    logger.info('[StripeService] Customer created:', { userId, stripeCustomerId: stripeCustomer.id });

    const row = result.rows[0];
    return {
      id: row.id as string,
      userId: row.user_id as string,
      stripeCustomerId: row.stripe_customer_id as string,
      email: row.email as string | undefined,
      defaultPaymentMethodId: row.default_payment_method_id as string | undefined,
      metadata: row.metadata as Record<string, unknown> | undefined,
      createdAt: new Date(row.created_at as string),
      updatedAt: new Date(row.updated_at as string),
    };
  }

  async getCustomerByUserId(userId: string): Promise<StripeCustomer | null> {
    const result = await db.query<Record<string, unknown>>(
      `SELECT * FROM financial.stripe_customers WHERE user_id = $1`,
      [userId]
    );

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

    const row = result.rows[0];
    return {
      id: row.id as string,
      userId: row.user_id as string,
      stripeCustomerId: row.stripe_customer_id as string,
      email: row.email as string | undefined,
      defaultPaymentMethodId: row.default_payment_method_id as string | undefined,
      metadata: row.metadata as Record<string, unknown> | undefined,
      createdAt: new Date(row.created_at as string),
      updatedAt: new Date(row.updated_at as string),
    };
  }

  async updateDefaultPaymentMethod(userId: string, paymentMethodId: string): Promise<void> {
    const customer = await this.getCustomerByUserId(userId);
    if (!customer) throw new Error('Customer not found');

    // Update in Stripe
    await stripe.customers.update(customer.stripeCustomerId, {
      invoice_settings: { default_payment_method: paymentMethodId },
    });

    // Update in database
    await db.query(
      `UPDATE financial.stripe_customers SET default_payment_method_id = $1 WHERE user_id = $2`,
      [paymentMethodId, userId]
    );
  }

  // ==========================================================================
  // Payment Methods
  // ==========================================================================

  async listPaymentMethods(userId: string): Promise<Stripe.PaymentMethod[]> {
    const customer = await this.getCustomerByUserId(userId);
    if (!customer) return [];

    const paymentMethods = await stripe.paymentMethods.list({
      customer: customer.stripeCustomerId,
      type: 'card',
    });

    return paymentMethods.data;
  }

  async attachPaymentMethod(userId: string, paymentMethodId: string): Promise<Stripe.PaymentMethod> {
    const customer = await this.getOrCreateCustomer(userId, '');

    const paymentMethod = await stripe.paymentMethods.attach(paymentMethodId, {
      customer: customer.stripeCustomerId,
    });

    return paymentMethod;
  }

  async detachPaymentMethod(paymentMethodId: string): Promise<void> {
    await stripe.paymentMethods.detach(paymentMethodId);
  }

  // ==========================================================================
  // Checkout Sessions
  // ==========================================================================

  async createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession> {
    // Get user email
    const userResult = await db.query<{ email: string }>(
      `SELECT email FROM public.users WHERE id = $1`,
      [input.userId]
    );

    if (userResult.rows.length === 0) {
      throw new Error('User not found');
    }

    const email = userResult.rows[0].email;
    const customer = await this.getOrCreateCustomer(input.userId, email);

    const sessionConfig: Stripe.Checkout.SessionCreateParams = {
      customer: customer.stripeCustomerId,
      success_url: input.successUrl,
      cancel_url: input.cancelUrl,
      mode: 'subscription',
      line_items: [],
      metadata: { userId: input.userId },
    };

    // Handle subscription checkout
    if (input.planId) {
      const plan = await this.getPlanById(input.planId);
      if (!plan) throw new Error('Plan not found');

      const priceId = input.billingCycle === 'yearly'
        ? plan.stripePriceIdYearly
        : plan.stripePriceIdMonthly;

      if (!priceId) throw new Error('Stripe price not configured for this plan');

      sessionConfig.line_items = [{ price: priceId, quantity: 1 }];
      sessionConfig.metadata!.planId = input.planId;
      sessionConfig.metadata!.billingCycle = input.billingCycle || 'monthly';
    }

    // Handle course purchase
    if (input.courseId) {
      sessionConfig.mode = 'payment';
      const courseResult = await db.query<{ price: string; title: string }>(
        `SELECT price, title FROM education.courses WHERE id = $1`,
        [input.courseId]
      );

      if (courseResult.rows.length === 0) throw new Error('Course not found');

      const course = courseResult.rows[0];
      sessionConfig.line_items = [{
        price_data: {
          currency: 'usd',
          product_data: { name: course.title },
          unit_amount: Math.round(parseFloat(course.price) * 100),
        },
        quantity: 1,
      }];
      sessionConfig.metadata!.courseId = input.courseId;
    }

    // Apply promo code
    if (input.promoCode) {
      const promo = await this.getStripeCouponByCode(input.promoCode);
      if (promo) {
        sessionConfig.discounts = [{ coupon: promo }];
      }
    }

    const session = await stripe.checkout.sessions.create(sessionConfig);

    logger.info('[StripeService] Checkout session created:', {
      sessionId: session.id,
      userId: input.userId
    });

    return {
      sessionId: session.id,
      url: session.url!,
      expiresAt: new Date(session.expires_at * 1000),
    };
  }

  async getStripeCouponByCode(code: string): Promise<string | null> {
    try {
      const promotionCodes = await stripe.promotionCodes.list({ code, limit: 1 });
      if (promotionCodes.data.length > 0 && promotionCodes.data[0].active) {
        return promotionCodes.data[0].coupon.id;
      }
      return null;
    } catch {
      return null;
    }
  }

  // ==========================================================================
  // Billing Portal
  // ==========================================================================

  async createBillingPortalSession(userId: string, returnUrl: string): Promise<BillingPortalSession> {
    const customer = await this.getCustomerByUserId(userId);
    if (!customer) throw new Error('Customer not found');

    const session = await stripe.billingPortal.sessions.create({
      customer: customer.stripeCustomerId,
      return_url: returnUrl,
    });

    return {
      url: session.url,
      returnUrl,
    };
  }

  // ==========================================================================
  // Plans
  // ==========================================================================

  async getPlans(): Promise<SubscriptionPlan[]> {
    const result = await db.query<Record<string, unknown>>(
      `SELECT * FROM financial.subscription_plans WHERE is_active = true ORDER BY sort_order`
    );
    return result.rows.map(transformPlan);
  }

  async getPlanById(id: string): Promise<SubscriptionPlan | null> {
    const result = await db.query<Record<string, unknown>>(
      `SELECT * FROM financial.subscription_plans WHERE id = $1`,
      [id]
    );
    if (result.rows.length === 0) return null;
    return transformPlan(result.rows[0]);
  }

  async getPlanBySlug(slug: string): Promise<SubscriptionPlan | null> {
    const result = await db.query<Record<string, unknown>>(
      `SELECT * FROM financial.subscription_plans WHERE slug = $1`,
      [slug]
    );
    if (result.rows.length === 0) return null;
    return transformPlan(result.rows[0]);
  }

  // ==========================================================================
  // Payment Intents
  // ==========================================================================

  async createPaymentIntent(
    userId: string,
    amount: number,
    currency: string = 'usd',
    metadata: Record<string, string> = {}
  ): Promise<Stripe.PaymentIntent> {
    const userResult = await db.query<{ email: string }>(
      `SELECT email FROM public.users WHERE id = $1`,
      [userId]
    );

    if (userResult.rows.length === 0) throw new Error('User not found');

    const customer = await this.getOrCreateCustomer(userId, userResult.rows[0].email);

    const paymentIntent = await stripe.paymentIntents.create({
      amount: Math.round(amount * 100),
      currency,
      customer: customer.stripeCustomerId,
      metadata: { userId, ...metadata },
    });

    return paymentIntent;
  }

  async confirmPaymentIntent(paymentIntentId: string, paymentMethodId: string): Promise<Stripe.PaymentIntent> {
    return stripe.paymentIntents.confirm(paymentIntentId, {
      payment_method: paymentMethodId,
    });
  }

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

  async cancelSubscription(stripeSubscriptionId: string, immediately: boolean = false): Promise<Stripe.Subscription> {
    if (immediately) {
      return stripe.subscriptions.cancel(stripeSubscriptionId);
    } else {
      return stripe.subscriptions.update(stripeSubscriptionId, {
        cancel_at_period_end: true,
      });
    }
  }

  async resumeSubscription(stripeSubscriptionId: string): Promise<Stripe.Subscription> {
    return stripe.subscriptions.update(stripeSubscriptionId, {
      cancel_at_period_end: false,
    });
  }

  async updateSubscriptionPlan(stripeSubscriptionId: string, newPriceId: string): Promise<Stripe.Subscription> {
    const subscription = await stripe.subscriptions.retrieve(stripeSubscriptionId);

    return stripe.subscriptions.update(stripeSubscriptionId, {
      items: [{
        id: subscription.items.data[0].id,
        price: newPriceId,
      }],
      proration_behavior: 'create_prorations',
    });
  }

  // ==========================================================================
  // Refunds
  // ==========================================================================

  async createRefund(chargeId: string, amount?: number, reason?: string): Promise<Stripe.Refund> {
    return stripe.refunds.create({
      charge: chargeId,
      amount: amount ? Math.round(amount * 100) : undefined,
      reason: reason as Stripe.RefundCreateParams.Reason,
    });
  }

  // ==========================================================================
  // Webhooks
  // ==========================================================================

  constructWebhookEvent(payload: string | Buffer, signature: string): Stripe.Event {
    const webhookSecret = config.stripe?.webhookSecret || process.env.STRIPE_WEBHOOK_SECRET || '';
    return stripe.webhooks.constructEvent(payload, signature, webhookSecret);
  }

  // ==========================================================================
  // Invoices
  // ==========================================================================

  async getInvoice(invoiceId: string): Promise<Stripe.Invoice> {
    return stripe.invoices.retrieve(invoiceId);
  }

  async listInvoices(customerId: string, limit: number = 10): Promise<Stripe.Invoice[]> {
    const invoices = await stripe.invoices.list({
      customer: customerId,
      limit,
    });
    return invoices.data;
  }
}

export const stripeService = new StripeService();
