import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common';
import { PrismaService } from '../common/prisma.service';
import { ShopifyService } from '../integrations/shopify.service';
import { getMerchant, logEmail, payableBalance } from '../common/core';

@Controller('admin')
export class PayoutsController {
  constructor(
    private prisma: PrismaService,
    private shopify: ShopifyService,
  ) {}

  @Get('payouts')
  async payouts() {
    const m = await getMerchant(this.prisma);
    const [owed, requests, paid] = await Promise.all([
      this.prisma.affiliate.findMany({
        where: { merchantId: m.id, balance: { gt: 0 } },
        orderBy: { balance: 'desc' },
        select: { id: true, name: true, email: true, balance: true },
      }),
      this.prisma.payout.findMany({
        where: { merchantId: m.id, status: 'REQUESTED' },
        include: { affiliate: { select: { name: true } } },
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.payout.findMany({
        where: { merchantId: m.id, status: 'PAID' },
        include: { affiliate: { select: { name: true } } },
        orderBy: { createdAt: 'desc' },
        take: 100,
      }),
    ]);

    // Lifetime total paid + method-wise breakdown
    const byMethod: Record<string, { count: number; amount: number }> = {};
    let totalPaid = 0;
    for (const p of paid) {
      const amt = Number(p.amount);
      totalPaid += amt;
      const key = p.method ?? 'MANUAL';
      byMethod[key] = byMethod[key] || { count: 0, amount: 0 };
      byMethod[key].count += 1;
      byMethod[key].amount += amt;
    }

    return {
      minPayout: Number(m.minPayout),
      owed: owed.map((a) => ({ ...a, balance: Number(a.balance) })),
      requests: requests.map((p) => ({
        id: p.id,
        affiliate: p.affiliate.name,
        amount: Number(p.amount),
        date: p.createdAt.toISOString().slice(0, 10),
      })),
      totalPaid,
      methodBreakdown: Object.entries(byMethod).map(([method, v]) => ({ method, count: v.count, amount: v.amount })),
      history: paid.map((p) => ({
        id: p.id,
        affiliate: p.affiliate.name,
        amount: Number(p.amount),
        method: p.method ?? 'MANUAL',
        reference: p.reference ?? null,
        date: p.createdAt.toISOString().slice(0, 10),
      })),
    };
  }

  // --- Invoice for a payout (PRD 4.8) ---
  @Get('payouts/:id/invoice')
  async payoutInvoice(@Param('id') id: string) {
    const p = await this.prisma.payout.findUnique({
      where: { id },
      include: { affiliate: { select: { name: true, email: true } }, merchant: { select: { programName: true, shop: true } } },
    });
    if (!p) return null;
    return {
      invoiceNumber: 'INV-' + id.slice(-8).toUpperCase(),
      date: (p.paidAt || p.createdAt).toISOString().slice(0, 10),
      program: p.merchant.programName || p.merchant.shop,
      shop: p.merchant.shop,
      affiliate: p.affiliate.name,
      email: p.affiliate.email,
      amount: Number(p.amount),
      method: p.method,
      reference: p.reference,
      status: p.status,
    };
  }

  @Post('affiliates/:id/mark-paid')
  async markPaid(@Param('id') id: string) {
    const a = await this.prisma.affiliate.findUnique({ where: { id } });
    if (!a) return { ok: false };
    const m = await getMerchant(this.prisma);
    // Pay only the payable (past commission-hold) part; held stays in balance.
    const { payable } = await payableBalance(this.prisma, a, m.commissionHoldDays);
    const amount = payable;
    if (amount <= 0) return { ok: false, message: 'No payable balance (commission may still be on hold)' };

    // Choose the payout rail based on the affiliate's selected method.
    let method: 'STORE_CREDIT' | 'PAYPAL' | 'MANUAL' = 'MANUAL';
    let reference: string | null = null;

    const pref = a.paymentMethod as string | null;
    if (pref === 'STORE_CREDIT') {
      if (!this.shopify.configured() && !m.shopifyAccessToken) {
        return { ok: false, message: 'Connect Shopify to pay with store credit' };
      }
      // Prefer a REAL gift card (spendable store credit); fall back to a fixed
      // discount code if Gift Cards aren't available on the store's plan.
      const gc = await this.shopify.createGiftCard(amount, `Affiliate payout — ${a.name}`, m.shop, m.shopifyAccessToken ?? undefined);
      if (gc.code) {
        method = 'STORE_CREDIT';
        reference = `giftcard:${gc.code}`;
      } else {
        const code = `SC-${(a.couponCode || a.name.split(' ')[0]).toUpperCase()}-${Date.now().toString().slice(-5)}`;
        try {
          await this.shopify.createDiscountCode(code, 'FIXED', amount);
        } catch (e) {
          return { ok: false, message: 'Shopify store-credit failed: ' + (e as Error).message };
        }
        method = 'STORE_CREDIT';
        reference = code;
      }
    } else if (pref === 'PAYPAL') {
      method = 'PAYPAL';
      reference = (a.paymentDetails as any)?.info || null;
    }

    await this.prisma.$transaction([
      this.prisma.payout.create({
        data: { merchantId: m.id, affiliateId: id, amount, status: 'PAID', method: method as any, reference, paidAt: new Date() },
      }),
      this.prisma.payout.updateMany({
        where: { affiliateId: id, status: 'REQUESTED' },
        data: { status: 'PAID', paidAt: new Date() },
      }),
      this.prisma.affiliate.update({ where: { id }, data: { balance: { decrement: amount } } }),
    ]);
    if ((a.notifyPrefs as any)?.payout !== false) {
      await logEmail(
        this.prisma,
        m.id,
        a.email,
        'payout',
        method === 'STORE_CREDIT' ? `Store credit ${reference} for ₹${amount}` : `You've been paid ₹${amount}`,
      );
    }
    return { ok: true, paid: amount, method, reference };
  }

  // --- Payment settings (PRD 4.8) — which payout methods are offered ---
  @Get('payment-settings')
  async getPaymentSettings() {
    const m = await getMerchant(this.prisma);
    return { payoutMethods: m.payoutMethods, minPayout: Number(m.minPayout) };
  }

  @Put('payment-settings')
  async savePaymentSettings(@Body() body: { payoutMethods?: string[]; minPayout?: number }) {
    const m = await getMerchant(this.prisma);
    await this.prisma.merchant.update({
      where: { id: m.id },
      data: {
        payoutMethods: Array.isArray(body.payoutMethods) && body.payoutMethods.length ? body.payoutMethods : m.payoutMethods,
        minPayout: Number(body.minPayout ?? m.minPayout),
      },
    });
    return { ok: true };
  }
}
