/**
 * Subscription Service
 * Handles subscription management and billing cycles
 */

import { db } from '../../../shared/database';
import { logger } from '../../../shared/utils/logger';
import { stripeService } from './stripe.service';
import type {
  Subscription,
  SubscriptionWithPlan,
  CreateSubscriptionInput,
  SubscriptionStatus,
  SubscriptionPlan,
  BillingCycle,
} from '../types/payments.types';

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

function transformSubscription(row: Record<string, unknown>): Subscription {
  return {
    id: row.id as string,
    userId: row.user_id as string,
    planId: row.plan_id as string,
    stripeSubscriptionId: row.stripe_subscription_id as string | undefined,
    stripeCustomerId: row.stripe_customer_id as string | undefined,
    status: row.status as SubscriptionStatus,
    billingCycle: row.billing_cycle as BillingCycle,
    currentPeriodStart: row.current_period_start ? new Date(row.current_period_start as string) : undefined,
    currentPeriodEnd: row.current_period_end ? new Date(row.current_period_end as string) : undefined,
    trialStart: row.trial_start ? new Date(row.trial_start as string) : undefined,
    trialEnd: row.trial_end ? new Date(row.trial_end as string) : undefined,
    cancelAtPeriodEnd: row.cancel_at_period_end as boolean,
    cancelledAt: row.cancelled_at ? new Date(row.cancelled_at as string) : undefined,
    cancellationReason: row.cancellation_reason as string | undefined,
    currentPrice: row.current_price ? parseFloat(row.current_price as string) : undefined,
    currency: row.currency as string,
    createdAt: new Date(row.created_at as string),
    updatedAt: new Date(row.updated_at as string),
  };
}

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),
  };
}

// ============================================================================
// Subscription Service Class
// ============================================================================

class SubscriptionService {
  // ==========================================================================
  // Subscription Queries
  // ==========================================================================

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

  async getSubscriptionByUserId(userId: string): Promise<SubscriptionWithPlan | null> {
    const result = await db.query<Record<string, unknown>>(
      `SELECT s.*, p.name as plan_name, p.slug as plan_slug, p.description as plan_description,
              p.price_monthly, p.price_yearly, p.features, p.max_watchlists, p.max_alerts,
              p.ml_predictions_access, p.signals_access, p.backtesting_access, p.api_access,
              p.priority_support, p.courses_access, p.sort_order, p.is_featured,
              p.stripe_price_id_monthly, p.stripe_price_id_yearly, p.stripe_product_id
       FROM financial.subscriptions s
       JOIN financial.subscription_plans p ON s.plan_id = p.id
       WHERE s.user_id = $1 AND s.status IN ('active', 'trialing', 'past_due')
       ORDER BY s.created_at DESC
       LIMIT 1`,
      [userId]
    );

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

    const row = result.rows[0];
    const subscription = transformSubscription(row);
    const plan: SubscriptionPlan = {
      id: row.plan_id as string,
      name: row.plan_name as string,
      slug: row.plan_slug as string,
      description: row.plan_description as string | undefined,
      priceMonthly: parseFloat(row.price_monthly as string),
      priceYearly: row.price_yearly ? parseFloat(row.price_yearly as string) : undefined,
      currency: subscription.currency,
      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: true,
      createdAt: subscription.createdAt,
      updatedAt: subscription.updatedAt,
    };

    return { ...subscription, plan };
  }

  async getSubscriptionHistory(userId: string): Promise<Subscription[]> {
    const result = await db.query<Record<string, unknown>>(
      `SELECT * FROM financial.subscriptions
       WHERE user_id = $1
       ORDER BY created_at DESC`,
      [userId]
    );
    return result.rows.map(transformSubscription);
  }

  async hasActiveSubscription(userId: string): Promise<boolean> {
    const result = await db.query<{ exists: boolean }>(
      `SELECT EXISTS(
        SELECT 1 FROM financial.subscriptions
        WHERE user_id = $1 AND status IN ('active', 'trialing')
      ) as exists`,
      [userId]
    );
    return result.rows[0].exists;
  }

  // ==========================================================================
  // Plan Queries
  // ==========================================================================

  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]);
  }

  // ==========================================================================
  // Subscription Management
  // ==========================================================================

  async createSubscription(input: CreateSubscriptionInput): Promise<Subscription> {
    // Check for existing active subscription
    const existing = await this.getSubscriptionByUserId(input.userId);
    if (existing && existing.status === 'active') {
      throw new Error('User already has an active subscription');
    }

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

    const billingCycle = input.billingCycle || 'monthly';
    const price = billingCycle === 'yearly' ? (plan.priceYearly || plan.priceMonthly * 12) : plan.priceMonthly;

    // Calculate period
    const now = new Date();
    const periodEnd = new Date(now);
    if (billingCycle === 'yearly') {
      periodEnd.setFullYear(periodEnd.getFullYear() + 1);
    } else {
      periodEnd.setMonth(periodEnd.getMonth() + 1);
    }

    // Create subscription record
    const result = await db.query<Record<string, unknown>>(
      `INSERT INTO financial.subscriptions (
        user_id, plan_id, status, billing_cycle, current_period_start,
        current_period_end, current_price, currency
      ) VALUES ($1, $2, 'active', $3, $4, $5, $6, $7)
      RETURNING *`,
      [
        input.userId,
        input.planId,
        billingCycle,
        now,
        periodEnd,
        price,
        plan.currency,
      ]
    );

    logger.info('[SubscriptionService] Subscription created:', {
      userId: input.userId,
      planId: input.planId,
      billingCycle,
    });

    return transformSubscription(result.rows[0]);
  }

  async updateSubscriptionFromStripe(
    stripeSubscriptionId: string,
    data: {
      status?: SubscriptionStatus;
      currentPeriodStart?: Date;
      currentPeriodEnd?: Date;
      cancelAtPeriodEnd?: boolean;
      cancelledAt?: Date;
    }
  ): Promise<Subscription | null> {
    const updates: string[] = [];
    const params: (string | Date | boolean)[] = [];
    let paramIndex = 1;

    if (data.status !== undefined) {
      updates.push(`status = $${paramIndex++}`);
      params.push(data.status);
    }
    if (data.currentPeriodStart !== undefined) {
      updates.push(`current_period_start = $${paramIndex++}`);
      params.push(data.currentPeriodStart);
    }
    if (data.currentPeriodEnd !== undefined) {
      updates.push(`current_period_end = $${paramIndex++}`);
      params.push(data.currentPeriodEnd);
    }
    if (data.cancelAtPeriodEnd !== undefined) {
      updates.push(`cancel_at_period_end = $${paramIndex++}`);
      params.push(data.cancelAtPeriodEnd);
    }
    if (data.cancelledAt !== undefined) {
      updates.push(`cancelled_at = $${paramIndex++}`);
      params.push(data.cancelledAt);
    }

    if (updates.length === 0) return null;

    params.push(stripeSubscriptionId);
    const result = await db.query<Record<string, unknown>>(
      `UPDATE financial.subscriptions
       SET ${updates.join(', ')}
       WHERE stripe_subscription_id = $${paramIndex}
       RETURNING *`,
      params
    );

    if (result.rows.length === 0) return null;
    return transformSubscription(result.rows[0]);
  }

  async cancelSubscription(
    userId: string,
    immediately: boolean = false,
    reason?: string
  ): Promise<Subscription | null> {
    const subscription = await this.getSubscriptionByUserId(userId);
    if (!subscription) {
      throw new Error('No active subscription found');
    }

    // Cancel in Stripe if applicable
    if (subscription.stripeSubscriptionId) {
      await stripeService.cancelSubscription(subscription.stripeSubscriptionId, immediately);
    }

    // Update local record
    if (immediately) {
      const result = await db.query<Record<string, unknown>>(
        `UPDATE financial.subscriptions
         SET status = 'cancelled',
             cancelled_at = CURRENT_TIMESTAMP,
             cancellation_reason = $1
         WHERE id = $2
         RETURNING *`,
        [reason, subscription.id]
      );

      logger.info('[SubscriptionService] Subscription cancelled immediately:', {
        subscriptionId: subscription.id,
      });

      return transformSubscription(result.rows[0]);
    } else {
      const result = await db.query<Record<string, unknown>>(
        `UPDATE financial.subscriptions
         SET cancel_at_period_end = true,
             cancellation_reason = $1
         WHERE id = $2
         RETURNING *`,
        [reason, subscription.id]
      );

      logger.info('[SubscriptionService] Subscription set to cancel at period end:', {
        subscriptionId: subscription.id,
      });

      return transformSubscription(result.rows[0]);
    }
  }

  async resumeSubscription(userId: string): Promise<Subscription | null> {
    const subscription = await this.getSubscriptionByUserId(userId);
    if (!subscription) {
      throw new Error('No subscription found');
    }

    if (!subscription.cancelAtPeriodEnd) {
      throw new Error('Subscription is not set to cancel');
    }

    // Resume in Stripe if applicable
    if (subscription.stripeSubscriptionId) {
      await stripeService.resumeSubscription(subscription.stripeSubscriptionId);
    }

    // Update local record
    const result = await db.query<Record<string, unknown>>(
      `UPDATE financial.subscriptions
       SET cancel_at_period_end = false,
           cancellation_reason = NULL
       WHERE id = $1
       RETURNING *`,
      [subscription.id]
    );

    logger.info('[SubscriptionService] Subscription resumed:', {
      subscriptionId: subscription.id,
    });

    return transformSubscription(result.rows[0]);
  }

  async changePlan(
    userId: string,
    newPlanId: string,
    billingCycle?: BillingCycle
  ): Promise<Subscription | null> {
    const subscription = await this.getSubscriptionByUserId(userId);
    if (!subscription) {
      throw new Error('No active subscription found');
    }

    const newPlan = await this.getPlanById(newPlanId);
    if (!newPlan) {
      throw new Error('Plan not found');
    }

    const newBillingCycle = billingCycle || subscription.billingCycle;
    const priceId = newBillingCycle === 'yearly'
      ? newPlan.stripePriceIdYearly
      : newPlan.stripePriceIdMonthly;

    // Update in Stripe if applicable
    if (subscription.stripeSubscriptionId && priceId) {
      await stripeService.updateSubscriptionPlan(subscription.stripeSubscriptionId, priceId);
    }

    // Calculate new price
    const newPrice = newBillingCycle === 'yearly'
      ? (newPlan.priceYearly || newPlan.priceMonthly * 12)
      : newPlan.priceMonthly;

    // Update local record
    const result = await db.query<Record<string, unknown>>(
      `UPDATE financial.subscriptions
       SET plan_id = $1,
           billing_cycle = $2,
           current_price = $3
       WHERE id = $4
       RETURNING *`,
      [newPlanId, newBillingCycle, newPrice, subscription.id]
    );

    logger.info('[SubscriptionService] Subscription plan changed:', {
      subscriptionId: subscription.id,
      oldPlanId: subscription.planId,
      newPlanId,
    });

    return transformSubscription(result.rows[0]);
  }

  // ==========================================================================
  // Feature Access Checks
  // ==========================================================================

  async hasFeatureAccess(userId: string, feature: keyof SubscriptionPlan): Promise<boolean> {
    const subscription = await this.getSubscriptionByUserId(userId);
    if (!subscription || !['active', 'trialing'].includes(subscription.status)) {
      return false;
    }

    const value = subscription.plan[feature];
    if (typeof value === 'boolean') {
      return value;
    }
    if (typeof value === 'number') {
      return value > 0;
    }
    if (typeof value === 'string') {
      return value !== 'none';
    }
    return false;
  }

  async getUserPlanFeatures(userId: string): Promise<Partial<SubscriptionPlan> | null> {
    const subscription = await this.getSubscriptionByUserId(userId);
    if (!subscription) return null;

    return {
      name: subscription.plan.name,
      slug: subscription.plan.slug,
      maxWatchlists: subscription.plan.maxWatchlists,
      maxAlerts: subscription.plan.maxAlerts,
      mlPredictionsAccess: subscription.plan.mlPredictionsAccess,
      signalsAccess: subscription.plan.signalsAccess,
      backtestingAccess: subscription.plan.backtestingAccess,
      apiAccess: subscription.plan.apiAccess,
      prioritySupport: subscription.plan.prioritySupport,
      coursesAccess: subscription.plan.coursesAccess,
    };
  }

  // ==========================================================================
  // Statistics
  // ==========================================================================

  async getSubscriptionStats(): Promise<{
    totalActive: number;
    totalTrialing: number;
    totalCancelled: number;
    monthlyRevenue: number;
    planDistribution: { planId: string; planName: string; count: number }[];
  }> {
    const statusResult = await db.query<Record<string, string>>(
      `SELECT
        COUNT(*) FILTER (WHERE status = 'active') as total_active,
        COUNT(*) FILTER (WHERE status = 'trialing') as total_trialing,
        COUNT(*) FILTER (WHERE status = 'cancelled') as total_cancelled,
        COALESCE(SUM(current_price) FILTER (WHERE status = 'active' AND billing_cycle = 'monthly'), 0) as monthly_revenue
       FROM financial.subscriptions`
    );

    const planResult = await db.query<{ plan_id: string; plan_name: string; count: string }>(
      `SELECT s.plan_id, p.name as plan_name, COUNT(*) as count
       FROM financial.subscriptions s
       JOIN financial.subscription_plans p ON s.plan_id = p.id
       WHERE s.status IN ('active', 'trialing')
       GROUP BY s.plan_id, p.name
       ORDER BY count DESC`
    );

    const stats = statusResult.rows[0];
    return {
      totalActive: parseInt(stats.total_active, 10),
      totalTrialing: parseInt(stats.total_trialing, 10),
      totalCancelled: parseInt(stats.total_cancelled, 10),
      monthlyRevenue: parseFloat(stats.monthly_revenue) || 0,
      planDistribution: planResult.rows.map((row) => ({
        planId: row.plan_id,
        planName: row.plan_name,
        count: parseInt(row.count, 10),
      })),
    };
  }
}

export const subscriptionService = new SubscriptionService();
