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

/** Public base for referral links (never localhost in production). */
function appBase(): string {
  return (process.env.SHOPIFY_APP_URL || process.env.WEB_URL || '').replace(/\/$/, '');
}

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

  // --- Coupons overview + bulk tools (PRD 4.6) ---
  @Get('coupons')
  async coupons() {
    const m = await getMerchant(this.prisma);
    const list = await this.prisma.affiliate.findMany({
      where: { merchantId: m.id, couponCode: { not: null } },
      orderBy: { createdAt: 'desc' },
    });
    return list.map((a) => ({
      id: a.id,
      name: a.name,
      couponCode: a.couponCode,
      discountType: a.couponDiscountType,
      discountValue: a.couponDiscountValue == null ? null : Number(a.couponDiscountValue),
      usageLimit: a.couponUsageLimitPerCustomer,
      maxRedemptions: a.couponMaxRedemptions,
      newCustomersOnly: a.couponNewCustomersOnly,
      minOrderValue: a.couponMinOrderValue == null ? null : Number(a.couponMinOrderValue),
      minCartQty: a.couponMinCartQty ?? null,
      expiresAt: a.couponExpiresAt ? a.couponExpiresAt.toISOString().slice(0, 10) : null,
      combines: (a.couponCombines as Record<string, boolean>) ?? { product: false, order: false, shipping: false },
      personal: a.couponPersonal,
      status: a.status,
    }));
  }

  /** Affiliates available to assign a coupon to (for the create dropdown). */
  @Get('coupons/assignable')
  async assignableAffiliates() {
    const m = await getMerchant(this.prisma);
    const list = await this.prisma.affiliate.findMany({
      where: { merchantId: m.id },
      orderBy: { name: 'asc' },
      select: { id: true, name: true, email: true, couponCode: true },
    });
    return list.map((a) => ({ id: a.id, name: a.name, email: a.email, hasCoupon: !!a.couponCode }));
  }

  /** Create / assign a coupon to an affiliate (GoAffPro "Coupon Based Commissions"). */
  @Post('coupons')
  async createCoupon(@Body() body: Record<string, any>) {
    const m = await getMerchant(this.prisma);
    if (!body.affiliateId) throw new BadRequestException('Choose an affiliate');
    const code = String(body.couponCode || '').trim().toUpperCase();
    if (code.length < 3) throw new BadRequestException('Coupon code must be at least 3 characters');

    const aff = await this.prisma.affiliate.findFirst({ where: { id: body.affiliateId, merchantId: m.id } });
    if (!aff) throw new BadRequestException('Affiliate not found');

    // Code must be unique across the merchant's affiliates.
    const clash = await this.prisma.affiliate.findFirst({
      where: { merchantId: m.id, couponCode: { equals: code, mode: 'insensitive' }, NOT: { id: aff.id } },
    });
    if (clash) throw new BadRequestException(`Code "${code}" is already used by ${clash.name}`);

    const num = (v: any) => (v === undefined || v === null || v === '' ? null : Number(v));
    await this.prisma.affiliate.update({
      where: { id: aff.id },
      data: {
        couponCode: code,
        referralLink: `${appBase()}/api/r/${code}`,
        couponDiscountType: body.discountType || 'PERCENT',
        couponDiscountValue: num(body.discountValue) ?? 10,
        couponUsageLimitPerCustomer: num(body.usageLimitPerCustomer),
        couponMaxRedemptions: num(body.maxRedemptions),
        couponNewCustomersOnly: !!body.newCustomersOnly,
        couponMinOrderValue: num(body.minOrderValue),
        couponMinCartQty: num(body.minCartQty),
        couponExpiresAt: body.expiresAt ? new Date(body.expiresAt) : null,
        couponCombines: body.combines ?? { product: false, order: false, shipping: false },
        couponPersonal: !!body.personal,
      },
    });

    // Create the REAL discount code in THIS store so customers can use it at
    // checkout. Push by default; caller can opt out with pushToShopify:false.
    // Only ever uses the merchant's own expiring token — never the legacy env
    // token, which Shopify's Admin API now rejects.
    let shopify: { pushed: boolean; error?: string } = { pushed: false };
    if (body.pushToShopify !== false) {
      if (!m.shopifyAccessToken) {
        shopify = { pushed: false, error: 'Store not connected yet — reopen the app from your Shopify admin, then try again.' };
      } else {
        try {
          await this.shopify.createDiscountCode(code, (body.discountType || 'PERCENT') === 'FIXED' ? 'FIXED' : 'PERCENT', num(body.discountValue) ?? 10, {
            usageLimit: num(body.maxRedemptions),
            oncePerCustomer: num(body.usageLimitPerCustomer) != null && Number(body.usageLimitPerCustomer) <= 1,
            minOrderValue: num(body.minOrderValue),
            minCartQty: num(body.minCartQty),
            endsAt: body.expiresAt ? new Date(body.expiresAt).toISOString() : null,
            combines: body.combines ?? null,
            shop: m.shop,
            token: m.shopifyAccessToken,
          });
          shopify = { pushed: true };
        } catch (e) {
          shopify = { pushed: false, error: (e as Error).message };
        }
      }
    }
    return { ok: true, couponCode: code, shopify };
  }

  /** Edit an affiliate's coupon. */
  @Put('coupons/:id')
  async updateCoupon(@Param('id') id: string, @Body() body: Record<string, any>) {
    const m = await getMerchant(this.prisma);
    const aff = await this.prisma.affiliate.findFirst({ where: { id, merchantId: m.id } });
    if (!aff) throw new BadRequestException('Affiliate not found');
    const num = (v: any) => (v === undefined || v === null || v === '' ? null : Number(v));
    const data: any = {};
    if (body.couponCode !== undefined) {
      const code = String(body.couponCode || '').trim().toUpperCase();
      if (code.length < 3) throw new BadRequestException('Coupon code must be at least 3 characters');
      const clash = await this.prisma.affiliate.findFirst({
        where: { merchantId: m.id, couponCode: { equals: code, mode: 'insensitive' }, NOT: { id } },
      });
      if (clash) throw new BadRequestException(`Code "${code}" is already used by ${clash.name}`);
      data.couponCode = code;
      data.referralLink = `${appBase()}/api/r/${code}`;
    }
    if (body.discountType !== undefined) data.couponDiscountType = body.discountType || 'PERCENT';
    if (body.discountValue !== undefined) data.couponDiscountValue = num(body.discountValue) ?? 10;
    if (body.usageLimitPerCustomer !== undefined) data.couponUsageLimitPerCustomer = num(body.usageLimitPerCustomer);
    if (body.maxRedemptions !== undefined) data.couponMaxRedemptions = num(body.maxRedemptions);
    if (body.newCustomersOnly !== undefined) data.couponNewCustomersOnly = !!body.newCustomersOnly;
    if (body.minOrderValue !== undefined) data.couponMinOrderValue = num(body.minOrderValue);
    if (body.minCartQty !== undefined) data.couponMinCartQty = num(body.minCartQty);
    if (body.expiresAt !== undefined) data.couponExpiresAt = body.expiresAt ? new Date(body.expiresAt) : null;
    if (body.combines !== undefined) data.couponCombines = body.combines ?? { product: false, order: false, shipping: false };
    if (body.personal !== undefined) data.couponPersonal = !!body.personal;
    await this.prisma.affiliate.update({ where: { id }, data });
    return { ok: true };
  }

  /** Remove an affiliate's coupon. */
  @Delete('coupons/:id')
  async deleteCoupon(@Param('id') id: string) {
    const m = await getMerchant(this.prisma);
    const aff = await this.prisma.affiliate.findFirst({ where: { id, merchantId: m.id } });
    if (!aff) throw new BadRequestException('Affiliate not found');
    await this.prisma.affiliate.update({
      where: { id },
      data: { couponCode: null, referralLink: null, couponMinOrderValue: null, couponExpiresAt: null },
    });
    return { ok: true };
  }

  /** Automatic Coupons config (default discount applied to every new affiliate coupon). */
  @Get('auto-coupon')
  async getAutoCoupon() {
    const m = await getMerchant(this.prisma);
    const auto = (m.autoCoupon as Record<string, any> | null) ?? {};
    return {
      discountType: auto.discountType ?? 'PERCENT',
      discountValue: auto.discountValue ?? Number(m.defaultCommissionValue) ?? 10,
      singleUsePerCustomer: !!auto.singleUsePerCustomer,
      maxRedemptions: auto.maxRedemptions ?? '',
      minOrderValue: auto.minOrderValue ?? '',
      newCustomersOnly: !!auto.newCustomersOnly,
      autoApplyDiscount: m.autoApplyDiscount,
      defaultCouponCode: m.defaultCouponCode ?? '',
    };
  }

  @Put('auto-coupon')
  async saveAutoCoupon(@Body() body: Record<string, any>) {
    const m = await getMerchant(this.prisma);
    await this.prisma.merchant.update({
      where: { id: m.id },
      data: {
        autoCoupon: {
          discountType: body.discountType || 'PERCENT',
          discountValue: body.discountValue === '' || body.discountValue == null ? null : Number(body.discountValue),
          singleUsePerCustomer: !!body.singleUsePerCustomer,
          maxRedemptions: body.maxRedemptions === '' || body.maxRedemptions == null ? null : Number(body.maxRedemptions),
          minOrderValue: body.minOrderValue === '' || body.minOrderValue == null ? null : Number(body.minOrderValue),
          newCustomersOnly: !!body.newCustomersOnly,
        },
        autoApplyDiscount: !!body.autoApplyDiscount,
        defaultCouponCode: body.defaultCouponCode?.trim() ? String(body.defaultCouponCode).trim().toUpperCase() : null,
      },
    });
    return { ok: true };
  }

  @Post('coupons/bulk-update')
  async bulkUpdateCoupons(@Body() body: Record<string, any>) {
    const m = await getMerchant(this.prisma);
    const data: any = {};
    if (body.discountType) data.couponDiscountType = body.discountType;
    if (body.discountValue !== undefined && body.discountValue !== '') data.couponDiscountValue = Number(body.discountValue);
    if (body.usageLimit !== undefined && body.usageLimit !== '') data.couponUsageLimitPerCustomer = Number(body.usageLimit);
    if (body.newCustomersOnly !== undefined) data.couponNewCustomersOnly = !!body.newCustomersOnly;
    if (Object.keys(data).length === 0) return { ok: false, message: 'Nothing to update' };
    const where: any = { merchantId: m.id, couponCode: { not: null } };
    if (body.groupId) where.groupId = body.groupId;
    const r = await this.prisma.affiliate.updateMany({ where, data });
    return { ok: true, updated: r.count };
  }
}
