import { Injectable, NotFoundException, BadRequestException, Logger } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreditsService } from '../credits/credits.service';
import { TossPaymentsService } from '../payments/toss-payments.service';
import { ChangePlanDto } from './dto/change-plan.dto';
import { SubscribeAddonDto } from './dto/subscribe-addon.dto';

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

  constructor(
    private prisma: PrismaService,
    private creditsService: CreditsService,
    private tossPayments: TossPaymentsService,
  ) {}

  private async ensureSubscription(userId: number) {
    let subscription = await this.prisma.subscription.findUnique({
      where: { userId },
      include: { plan: true, pendingPlan: true },
    });
    if (!subscription) {
      const freePlan = await this.prisma.plan.findUnique({
        where: { planId: 'free' },
      });
      if (!freePlan) {
        throw new NotFoundException('Free plan not found. Please seed plans first.');
      }
      subscription = await this.prisma.subscription.create({
        data: {
          userId,
          planId: freePlan.id,
          status: 'active',
          currentPeriodStart: new Date(),
          tossCustomerKey: 'user_' + userId,
        },
        include: { plan: true, pendingPlan: true },
      });
    }
    return subscription;
  }

  async getCurrent(userId: number) {
    const subscription = await this.ensureSubscription(userId);
    const addonSubscriptions = await this.prisma.addonSubscription.findMany({
      where: { userId, isActive: true },
      include: { addon: true },
    });
    return {
      id: subscription.id,
      plan: subscription.plan,
      status: subscription.status,
      currentPeriodStart: subscription.currentPeriodStart,
      currentPeriodEnd: subscription.currentPeriodEnd,
      cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
      nextBillingDate: subscription.nextBillingDate,
      billingAnchorDay: subscription.billingAnchorDay,
      pendingPlan: subscription.pendingPlan || null,
      lastBillingStatus: subscription.lastBillingStatus,
      addons: addonSubscriptions.map((as) => ({
        id: as.id,
        addon: as.addon,
        quantity: as.quantity,
        isActive: as.isActive,
      })),
    };
  }

  async getPlans() {
    return this.prisma.plan.findMany({
      where: { isActive: true },
      orderBy: { price: 'asc' },
    });
  }

  /**
   * Calculate the next billing date based on anchor day.
   */
  private getNextBillingDate(anchorDay: number, fromDate?: Date): Date {
    const base = fromDate || new Date();
    const nextMonth = new Date(base.getFullYear(), base.getMonth() + 1, 1);
    const maxDay = new Date(nextMonth.getFullYear(), nextMonth.getMonth() + 1, 0).getDate();
    const day = Math.min(anchorDay, maxDay);
    return new Date(nextMonth.getFullYear(), nextMonth.getMonth(), day);
  }

  /**
   * Change plan with proration logic:
   * - Upgrade: charge prorated difference immediately
   * - Downgrade: defer to next billing date
   * - To free: immediate switch
   */
  async changePlan(userId: number, dto: ChangePlanDto) {
    const subscription = await this.ensureSubscription(userId);
    const currentPlan = subscription.plan;
    const newPlan = await this.prisma.plan.findUnique({
      where: { planId: dto.planId },
    });
    if (!newPlan) {
      throw new NotFoundException('Plan not found: ' + dto.planId);
    }
    if (subscription.planId === newPlan.id) {
      throw new BadRequestException('Already subscribed to this plan');
    }

    const oldPrice = currentPlan.price;
    const newPrice = newPlan.price;

    // === Case 1: Switching to Free plan ===
    if (newPlan.planId === 'free') {
      const updated = await this.prisma.subscription.update({
        where: { userId },
        data: {
          planId: newPlan.id,
          status: 'active',
          cancelAtPeriodEnd: false,
          pendingPlanId: null,
          currentPeriodStart: new Date(),
        },
        include: { plan: true, pendingPlan: true },
      });
      return {
        ...updated,
        message: 'Switched to Free plan',
        changeType: 'free',
      };
    }

    // === Case 2: First paid plan (from free) — set billing anchor ===
    if (currentPlan.planId === 'free') {
      const today = new Date();
      const rawDay = today.getDate();
      const anchorDay = rawDay > 28 ? 28 : rawDay;
      const nextBillingDate = this.getNextBillingDate(anchorDay, today);

      // Charge first month immediately
      const chargeResult = await this.chargeUser(userId, newPlan.price, `FreeFlow ${newPlan.name} first month`, 'plan', newPlan.id);

      if (!chargeResult.success) {
        throw new BadRequestException(
          chargeResult.message || 'Payment failed. Please check your payment method.',
        );
      }

      const updated = await this.prisma.subscription.update({
        where: { userId },
        data: {
          planId: newPlan.id,
          status: 'active',
          cancelAtPeriodEnd: false,
          pendingPlanId: null,
          billingAnchorDay: anchorDay,
          nextBillingDate,
          lastBillingStatus: 'success',
          currentPeriodStart: today,
          currentPeriodEnd: nextBillingDate,
        },
        include: { plan: true, pendingPlan: true },
      });

      // Record billing history
      await this.prisma.billingHistory.create({
        data: {
          userId,
          type: 'plan',
          referenceId: newPlan.id,
          amount: newPlan.price,
          status: 'success',
          tossOrderId: chargeResult.orderId,
          tossPaymentKey: chargeResult.paymentKey,
          description: `First month: ${newPlan.name}`,
          periodStart: today,
          periodEnd: nextBillingDate,
        },
      });

      if (newPlan.creditBonus > 0) {
        await this.creditsService.addBonus(
          userId,
          newPlan.creditBonus,
          'Plan subscription bonus: ' + newPlan.name,
        );
      }

      return {
        ...updated,
        message: 'First payment processed',
        changeType: 'first_payment',
        chargedAmount: newPlan.price,
        nextBillingDate,
      };
    }

    // === Case 3: Upgrade (newPrice > oldPrice) — charge proration immediately ===
    if (newPrice > oldPrice) {
      const today = new Date();
      const nextBilling = subscription.nextBillingDate
        ? new Date(subscription.nextBillingDate)
        : this.getNextBillingDate(subscription.billingAnchorDay, today);

      const remainingDays = Math.max(
        1,
        Math.ceil((nextBilling.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)),
      );
      const totalDays = 30; // standard month
      const prorationAmount = Math.ceil(((newPrice - oldPrice) * remainingDays) / totalDays);

      if (prorationAmount > 0) {
        const chargeResult = await this.chargeUser(
          userId,
          prorationAmount,
          `FreeFlow upgrade ${currentPlan.name} → ${newPlan.name} (proration)`,
          'upgrade_proration',
          newPlan.id,
        );

        if (!chargeResult.success) {
          throw new BadRequestException(
            chargeResult.message || 'Upgrade payment failed. Please check your payment method.',
          );
        }

        await this.prisma.billingHistory.create({
          data: {
            userId,
            type: 'upgrade_proration',
            referenceId: newPlan.id,
            amount: prorationAmount,
            status: 'success',
            tossOrderId: chargeResult.orderId,
            tossPaymentKey: chargeResult.paymentKey,
            description: `Upgrade proration: ${currentPlan.name} → ${newPlan.name} (${remainingDays} days)`,
            periodStart: today,
            periodEnd: nextBilling,
          },
        });
      }

      const updated = await this.prisma.subscription.update({
        where: { userId },
        data: {
          planId: newPlan.id,
          status: 'active',
          cancelAtPeriodEnd: false,
          pendingPlanId: null,
        },
        include: { plan: true, pendingPlan: true },
      });

      if (newPlan.creditBonus > 0) {
        await this.creditsService.addBonus(
          userId,
          newPlan.creditBonus,
          'Upgrade bonus: ' + newPlan.name,
        );
      }

      return {
        ...updated,
        message: `Upgraded to ${newPlan.name}`,
        changeType: 'upgrade',
        chargedAmount: prorationAmount,
        nextBillingDate: subscription.nextBillingDate,
      };
    }

    // === Case 4: Downgrade (newPrice < oldPrice) — defer to next billing ===
    const updated = await this.prisma.subscription.update({
      where: { userId },
      data: {
        pendingPlanId: newPlan.id,
        cancelAtPeriodEnd: false,
      },
      include: { plan: true, pendingPlan: true },
    });

    return {
      ...updated,
      message: `Downgrade to ${newPlan.name} scheduled for next billing date`,
      changeType: 'downgrade',
      chargedAmount: 0,
      nextBillingDate: subscription.nextBillingDate,
      pendingPlan: newPlan,
    };
  }

  /**
   * Charge user via their default payment method.
   */
  private async chargeUser(
    userId: number,
    amount: number,
    orderName: string,
    type: string,
    referenceId?: number,
  ): Promise<{ success: boolean; orderId?: string; paymentKey?: string; message?: string }> {
    const defaultMethod = await this.prisma.paymentMethod.findFirst({
      where: { userId, isDefault: true },
    });
    if (!defaultMethod || !defaultMethod.tossBillingKey) {
      return { success: false, message: 'No payment method registered' };
    }

    const customerKey = `user_${userId}`;
    const orderId = `${type}_${userId}_${Date.now()}`;

    try {
      const result = await this.tossPayments.chargeBillingKey(
        defaultMethod.tossBillingKey,
        customerKey,
        amount,
        orderName,
        orderId,
      );
      return {
        success: true,
        orderId,
        paymentKey: result.paymentKey || '',
      };
    } catch (err: any) {
      this.logger.error(`Charge failed for user ${userId}: ${JSON.stringify(err)}`);
      return {
        success: false,
        message: err?.message || 'Payment failed',
      };
    }
  }

  async cancelSubscription(userId: number) {
    await this.ensureSubscription(userId);
    const updated = await this.prisma.subscription.update({
      where: { userId },
      data: { cancelAtPeriodEnd: true },
      include: { plan: true, pendingPlan: true },
    });
    return updated;
  }

  async getAddons() {
    return this.prisma.addon.findMany({
      where: { isActive: true },
      orderBy: { price: 'asc' },
    });
  }

  async subscribeAddon(userId: number, dto: SubscribeAddonDto) {
    const addon = await this.prisma.addon.findUnique({
      where: { addonId: dto.addonId },
    });
    if (!addon) {
      throw new NotFoundException('Addon not found: ' + dto.addonId);
    }
    const existing = await this.prisma.addonSubscription.findUnique({
      where: { userId_addonId: { userId, addonId: addon.id } },
    });
    if (existing) {
      return this.prisma.addonSubscription.update({
        where: { id: existing.id },
        data: { isActive: true, quantity: dto.quantity || 1 },
        include: { addon: true },
      });
    }
    return this.prisma.addonSubscription.create({
      data: {
        userId,
        addonId: addon.id,
        quantity: dto.quantity || 1,
        isActive: true,
      },
      include: { addon: true },
    });
  }

  async updateAddonQuantity(userId: number, addonSubId: number, quantity: number) {
    const addonSub = await this.prisma.addonSubscription.findFirst({
      where: { id: addonSubId, userId },
    });
    if (!addonSub) {
      throw new NotFoundException('Addon subscription not found');
    }
    return this.prisma.addonSubscription.update({
      where: { id: addonSubId },
      data: { quantity },
      include: { addon: true },
    });
  }

  async unsubscribeAddon(userId: number, addonSubId: number) {
    const addonSub = await this.prisma.addonSubscription.findFirst({
      where: { id: addonSubId, userId },
    });
    if (!addonSub) {
      throw new NotFoundException('Addon subscription not found');
    }
    return this.prisma.addonSubscription.update({
      where: { id: addonSubId },
      data: { isActive: false },
      include: { addon: true },
    });
  }

  async getBillingHistory(userId: number) {
    return this.prisma.billingHistory.findMany({
      where: { userId },
      orderBy: { createdAt: 'desc' },
      take: 50,
    });
  }
}
