import { Injectable, Logger, NotFoundException } from "@nestjs/common";
import { PrismaService } from "../../database/prisma.service";
import { PaddleService, PaddleWebhookEvent } from "./paddle.service";
import {
  PlanType,
  BillingIntervalType,
  PLAN_LIMITS,
  PLANS,
  SubscriptionResponseDto,
} from "./dto/subscription.dto";
import { plainToInstance } from "class-transformer";

@Injectable()
export class SubscriptionsService {
  private readonly logger = new Logger(SubscriptionsService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly paddleService: PaddleService,
  ) {}

  /**
   * Get current subscription for user
   */
  async getCurrentSubscription(
    userId: string,
  ): Promise<SubscriptionResponseDto | null> {
    const subscription = await this.prisma.subscription.findUnique({
      where: { userId },
    });

    if (!subscription) {
      return null;
    }

    return plainToInstance(SubscriptionResponseDto, subscription);
  }

  /**
   * Get or create subscription record for user
   */
  async getOrCreateSubscription(userId: string) {
    let subscription = await this.prisma.subscription.findUnique({
      where: { userId },
    });

    if (!subscription) {
      subscription = await this.prisma.subscription.create({
        data: {
          userId,
          plan: "free",
        },
      });
    }

    return subscription;
  }

  /**
   * Get all available plans
   */
  getPlans() {
    return PLANS;
  }

  /**
   * Get checkout configuration (client-side token + price ID)
   */
  getCheckoutConfig(
    plan: PlanType,
    billingInterval: BillingIntervalType,
  ): { priceId: string; clientToken: string; environment: string } {
    if (plan === PlanType.FREE) {
      throw new Error("Cannot checkout for free plan");
    }

    const paddlePlan = plan as "plus" | "premium";
    const paddleInterval = billingInterval as "monthly" | "yearly";

    const priceId = this.paddleService.getPriceId(paddlePlan, paddleInterval);
    const clientToken = this.paddleService.getClientToken();
    const environment = this.paddleService.getEnvironment();

    return { priceId, clientToken, environment };
  }

  /**
   * Handle incoming Paddle webhook event
   */
  async handleWebhookEvent(event: PaddleWebhookEvent): Promise<void> {
    const eventType = event.event_type;
    const data = event.data;

    this.logger.log(`Processing Paddle webhook: ${eventType}`);

    switch (eventType) {
      case "subscription.created":
      case "subscription.updated":
        await this.handleSubscriptionUpdate(data);
        break;
      case "subscription.canceled":
        await this.handleSubscriptionCancelled(data);
        break;
      case "subscription.past_due":
        await this.handleSubscriptionPastDue(data);
        break;
      case "subscription.resumed":
        await this.handleSubscriptionResumed(data);
        break;
      case "transaction.completed":
        this.logger.log(`Transaction completed: ${String(data.id)}`);
        break;
      case "transaction.payment_failed":
        this.logger.warn(`Payment failed for transaction: ${String(data.id)}`);
        break;
      default:
        this.logger.debug(`Unhandled Paddle event: ${eventType}`);
    }
  }

  /**
   * Cancel subscription for user
   */
  async cancelSubscription(userId: string): Promise<void> {
    const subscription = await this.prisma.subscription.findUnique({
      where: { userId },
    });

    if (!subscription?.paddleSubscriptionId) {
      throw new NotFoundException("No active subscription found");
    }

    await this.paddleService.cancelSubscription(
      subscription.paddleSubscriptionId,
      "next_billing_period",
    );

    this.logger.log(`Cancellation requested for user ${userId}`);
  }

  // ── Private Handlers ──

  private async handleSubscriptionUpdate(
    data: Record<string, unknown>,
  ): Promise<void> {
    const paddleSubId = data.id as string;
    const customerId = data.customer_id as string;
    const status = data.status as string;
    const customData = data.custom_data as { userId?: string } | undefined;
    const items = data.items as Array<{ price: { id: string } }> | undefined;
    const currentBillingPeriod = data.current_billing_period as
      | { starts_at: string; ends_at: string }
      | undefined;

    const userId = customData?.userId;
    if (!userId) {
      this.logger.warn(
        `No userId in custom_data for subscription ${paddleSubId}`,
      );
      return;
    }

    // Determine plan from price ID
    const priceId = items?.[0]?.price?.id;
    let plan: PlanType = PlanType.FREE;
    let interval: BillingIntervalType = BillingIntervalType.MONTHLY;

    if (priceId) {
      const mapped = this.paddleService.mapPriceIdToPlan(priceId);
      if (mapped) {
        plan = mapped.plan as PlanType;
        interval = mapped.interval as BillingIntervalType;
      }
    }

    // Determine effective plan based on Paddle status
    const effectivePlan =
      status === "active" || status === "trialing" ? plan : PlanType.FREE;

    // Upsert subscription record
    await this.prisma.subscription.upsert({
      where: { userId },
      update: {
        paddleSubscriptionId: paddleSubId,
        paddleCustomerId: customerId,
        plan: effectivePlan,
        billingInterval: interval,
        paddlePriceId: priceId ?? null,
        currentPeriodStart: currentBillingPeriod?.starts_at
          ? new Date(currentBillingPeriod.starts_at)
          : null,
        currentPeriodEnd: currentBillingPeriod?.ends_at
          ? new Date(currentBillingPeriod.ends_at)
          : null,
        cancelledAt: null,
        cancelEffectiveDate: null,
      },
      create: {
        userId,
        paddleSubscriptionId: paddleSubId,
        paddleCustomerId: customerId,
        plan: effectivePlan,
        billingInterval: interval,
        paddlePriceId: priceId ?? null,
        currentPeriodStart: currentBillingPeriod?.starts_at
          ? new Date(currentBillingPeriod.starts_at)
          : null,
        currentPeriodEnd: currentBillingPeriod?.ends_at
          ? new Date(currentBillingPeriod.ends_at)
          : null,
      },
    });

    // Sync user subscription status
    await this.prisma.user.update({
      where: { id: userId },
      data: {
        subscriptionStatus: effectivePlan,
        subscriptionExpiresAt: currentBillingPeriod?.ends_at
          ? new Date(currentBillingPeriod.ends_at)
          : null,
      },
    });

    // Sync usage limits with plan
    await this.syncLimitsWithPlan(userId, effectivePlan);

    this.logger.log(
      `Subscription updated: user=${userId}, plan=${effectivePlan}, interval=${interval}`,
    );
  }

  private async handleSubscriptionCancelled(
    data: Record<string, unknown>,
  ): Promise<void> {
    const paddleSubId = data.id as string;
    const canceledAt = data.canceled_at as string | undefined;
    const currentBillingPeriod = data.current_billing_period as
      | { ends_at: string }
      | undefined;

    const subscription = await this.prisma.subscription.findUnique({
      where: { paddleSubscriptionId: paddleSubId },
    });

    if (!subscription) {
      this.logger.warn(`Subscription not found for cancel: ${paddleSubId}`);
      return;
    }

    const effectiveDate = currentBillingPeriod?.ends_at
      ? new Date(currentBillingPeriod.ends_at)
      : new Date();

    await this.prisma.subscription.update({
      where: { id: subscription.id },
      data: {
        plan: "cancelled",
        cancelledAt: canceledAt ? new Date(canceledAt) : new Date(),
        cancelEffectiveDate: effectiveDate,
      },
    });

    // Downgrade user to free
    await this.prisma.user.update({
      where: { id: subscription.userId },
      data: { subscriptionStatus: "free" },
    });

    await this.syncLimitsWithPlan(subscription.userId, PlanType.FREE);

    this.logger.log(
      `Subscription cancelled: user=${subscription.userId}, effective=${effectiveDate.toISOString()}`,
    );
  }

  private async handleSubscriptionPastDue(
    data: Record<string, unknown>,
  ): Promise<void> {
    const paddleSubId = data.id as string;

    const subscription = await this.prisma.subscription.findUnique({
      where: { paddleSubscriptionId: paddleSubId },
    });

    if (!subscription) {
      return;
    }

    await this.prisma.subscription.update({
      where: { id: subscription.id },
      data: { plan: "past_due" },
    });

    await this.prisma.user.update({
      where: { id: subscription.userId },
      data: { subscriptionStatus: "past_due" },
    });

    this.logger.warn(`Subscription past due: user=${subscription.userId}`);
  }

  private async handleSubscriptionResumed(
    data: Record<string, unknown>,
  ): Promise<void> {
    // Re-process as an update to restore the plan
    await this.handleSubscriptionUpdate(data);
  }

  /**
   * Sync usage limits with plan tier
   */
  public async syncLimitsWithPlan(
    userId: string,
    plan: PlanType,
  ): Promise<void> {
    const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS[PlanType.FREE];

    await this.prisma.usageLimit.upsert({
      where: { userId },
      update: {
        maxAnalyses: limits.maxAnalyses,
        maxCoupons: limits.maxCoupons,
      },
      create: {
        userId,
        analysisCount: 0,
        couponCount: 0,
        maxAnalyses: limits.maxAnalyses,
        maxCoupons: limits.maxCoupons,
        lastResetDate: new Date(),
      },
    });
  }
}
