import {
  ConflictException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { BillingCycle, type MerchantPricingPlan } from "@prisma/client";
import { newId } from "../common/ids/id.util";
import { PrismaService } from "../prisma/prisma.service";
import type { CreatePricingPlanDto } from "./dto/create-pricing-plan.dto";
import type { PricingPlanDto } from "./dto/pricing-plan.dto";

@Injectable()
export class PricingPlanService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Crea un plan nuevo y expira el plan activo anterior (si existe) en la misma
   * transacción. Garantiza la invariante "un solo plan activo por merchant".
   */
  async create(
    merchantId: string,
    dto: CreatePricingPlanDto,
  ): Promise<PricingPlanDto> {
    await this.assertMerchantExists(merchantId);
    const now = new Date();
    const plan = await this.prisma.$transaction(async (tx) => {
      await tx.merchantPricingPlan.updateMany({
        where: { merchantId, active: true },
        data: { active: false, validUntil: now },
      });
      return tx.merchantPricingPlan.create({
        data: {
          id: newId("pricingPlan"),
          merchantId,
          name: dto.name,
          validFrom: now,
          monthlyFee: dto.monthlyFee,
          monthlyFeeCurrency: dto.monthlyFeeCurrency ?? "PYG",
          txFeeFixed: dto.txFeeFixed,
          txFeePercentage: dto.txFeePercentage,
          txFeeCurrency: dto.txFeeCurrency ?? "PYG",
          includedTxPerMonth: dto.includedTxPerMonth ?? null,
          overQuotaTxFee: dto.overQuotaTxFee ?? null,
          billingCycle: dto.billingCycle ?? BillingCycle.monthly,
          free: dto.free ?? false,
          selfBillingEnabled: dto.selfBillingEnabled ?? false,
          active: true,
        },
      });
    });
    return this.toDto(plan);
  }

  async list(merchantId: string): Promise<PricingPlanDto[]> {
    await this.assertMerchantExists(merchantId);
    const rows = await this.prisma.merchantPricingPlan.findMany({
      where: { merchantId },
      orderBy: { validFrom: "desc" },
    });
    return rows.map((r) => this.toDto(r));
  }

  async getActive(merchantId: string): Promise<PricingPlanDto | null> {
    await this.assertMerchantExists(merchantId);
    const row = await this.prisma.merchantPricingPlan.findFirst({
      where: { merchantId, active: true },
      orderBy: { validFrom: "desc" },
    });
    return row ? this.toDto(row) : null;
  }

  async expire(merchantId: string, planId: string): Promise<PricingPlanDto> {
    const existing = await this.prisma.merchantPricingPlan.findFirst({
      where: { id: planId, merchantId },
    });
    if (!existing) throw new NotFoundException("Pricing plan not found");
    if (!existing.active) {
      throw new ConflictException("Plan ya expirado");
    }
    const updated = await this.prisma.merchantPricingPlan.update({
      where: { id: existing.id },
      data: { active: false, validUntil: new Date() },
    });
    return this.toDto(updated);
  }

  private async assertMerchantExists(merchantId: string) {
    const m = await this.prisma.merchant.findUnique({
      where: { id: merchantId },
      select: { id: true },
    });
    if (!m) throw new NotFoundException("Merchant not found");
  }

  private toDto(p: MerchantPricingPlan): PricingPlanDto {
    return {
      id: p.id,
      merchantId: p.merchantId,
      name: p.name,
      validFrom: p.validFrom.toISOString(),
      validUntil: p.validUntil ? p.validUntil.toISOString() : null,
      monthlyFee: p.monthlyFee.toString(),
      monthlyFeeCurrency: p.monthlyFeeCurrency,
      txFeeFixed: p.txFeeFixed.toString(),
      txFeePercentage: p.txFeePercentage.toString(),
      txFeeCurrency: p.txFeeCurrency,
      includedTxPerMonth: p.includedTxPerMonth,
      overQuotaTxFee: p.overQuotaTxFee ? p.overQuotaTxFee.toString() : null,
      billingCycle: p.billingCycle,
      free: p.free,
      selfBillingEnabled: p.selfBillingEnabled,
      active: p.active,
      createdAt: p.createdAt.toISOString(),
    };
  }
}
