import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
import { PrismaService } from '../common/prisma.service';
import { genCode, getMerchant, hashPassword, logEmail } from '../common/core';
import { MarcadeoProvisioning } from '../integrations/marcadeo-provisioning.service';
import { ShopifyService } from '../integrations/shopify.service';

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

  @Get('affiliates')
  async affiliates(@Query('status') status?: string, @Query('q') q?: string) {
    const m = await getMerchant(this.prisma);
    const list = await this.prisma.affiliate.findMany({
      where: {
        merchantId: m.id,
        ...(status ? { status: status as any } : {}),
        ...(q
          ? {
              OR: [
                { name: { contains: q, mode: 'insensitive' } },
                { email: { contains: q, mode: 'insensitive' } },
                { couponCode: { contains: q, mode: 'insensitive' } },
              ],
            }
          : {}),
      },
      orderBy: { createdAt: 'desc' },
      include: { _count: { select: { orders: true, clicks: true } } },
    });
    return list.map((a) => ({
      id: a.id,
      name: a.name,
      email: a.email,
      status: a.status,
      couponCode: a.couponCode,
      clicks: a._count.clicks,
      sales: a._count.orders,
      balance: Number(a.balance),
    }));
  }

  /** Approve one affiliate: activate, issue coupon, apply signup bonus, welcome email. */
  private async approveAffiliate(id: string) {
    const a = await this.prisma.affiliate.findUnique({ where: { id }, include: { group: true } });
    if (!a) return null;
    const m = await getMerchant(this.prisma);
    const firstApproval = a.status !== 'ACTIVE';
    const code = a.couponCode || genCode(a.name);
    const api = (process.env.SHOPIFY_APP_URL || process.env.WEB_URL || '').replace(/\/$/, '');
    const bonus = firstApproval && a.group?.signupBonus ? Number(a.group.signupBonus) : 0;
    // Apply the merchant's Automatic Coupons defaults (PRD 4.6) to the new coupon.
    const auto = (m.autoCoupon as Record<string, any> | null) ?? null;
    const autoData: any = {};
    if (auto && firstApproval) {
      autoData.couponDiscountType = auto.discountType || 'PERCENT';
      autoData.couponDiscountValue = auto.discountValue != null && auto.discountValue !== '' ? Number(auto.discountValue) : (a.couponDiscountValue ?? 10);
      autoData.couponUsageLimitPerCustomer = auto.singleUsePerCustomer ? 1 : null;
      autoData.couponMaxRedemptions = auto.maxRedemptions != null && auto.maxRedemptions !== '' ? Number(auto.maxRedemptions) : null;
      autoData.couponMinOrderValue = auto.minOrderValue != null && auto.minOrderValue !== '' ? Number(auto.minOrderValue) : null;
      autoData.couponNewCustomersOnly = !!auto.newCustomersOnly;
    }
    const updated = await this.prisma.affiliate.update({
      where: { id },
      data: {
        status: 'ACTIVE',
        approvedAt: a.approvedAt ?? new Date(),
        couponCode: code,
        couponDiscountValue: a.couponDiscountValue ?? 10,
        referralLink: a.referralLink || `${api}/api/r/${code}`,
        ...autoData,
        ...(bonus ? { balance: { increment: bonus } } : {}),
      },
    });
    await logEmail(this.prisma, m.id, a.email, 'welcome', `Welcome to ${m.programName ?? 'the program'}`);

    // Create the REAL Shopify discount code so the affiliate's coupon actually
    // works at checkout (and the resulting order is attributed). Best-effort —
    // never block approval; if the code already exists Shopify just errors here.
    if (m.shopifyAccessToken && updated.couponCode) {
      try {
        await this.shopify.createDiscountCode(
          updated.couponCode,
          (updated.couponDiscountType || 'PERCENT') === 'FIXED' ? 'FIXED' : 'PERCENT',
          updated.couponDiscountValue != null ? Number(updated.couponDiscountValue) : 10,
          {
            usageLimit: updated.couponMaxRedemptions ?? null,
            oncePerCustomer: updated.couponUsageLimitPerCustomer != null && Number(updated.couponUsageLimitPerCustomer) <= 1,
            minOrderValue: updated.couponMinOrderValue != null ? Number(updated.couponMinOrderValue) : null,
            endsAt: updated.couponExpiresAt ? updated.couponExpiresAt.toISOString() : null,
            shop: m.shop,
            token: m.shopifyAccessToken,
          },
        );
      } catch {
        /* fail-soft: coupon may already exist in the store, or push not permitted */
      }
    }

    // Mirror the approved affiliate into Marcadeo (publisher + tracking link + coupon). Fail-soft.
    await this.marcadeo.ensurePublisher(m, updated);
    return { code, bonus };
  }

  @Post('affiliates/:id/approve')
  async approve(@Param('id') id: string) {
    const r = await this.approveAffiliate(id);
    if (!r) return { ok: false };
    return { ok: true, couponCode: r.code, signupBonus: r.bonus };
  }

  @Post('affiliates/bulk-approve')
  async bulkApprove(@Body() body: { ids?: string[] }) {
    let approved = 0;
    for (const id of body.ids ?? []) {
      const r = await this.approveAffiliate(id);
      if (r) approved++;
    }
    return { ok: true, approved };
  }

  @Post('affiliates/bulk-reject')
  async bulkReject(@Body() body: { ids?: string[] }) {
    const ids = body.ids ?? [];
    const r = await this.prisma.affiliate.updateMany({ where: { id: { in: ids } }, data: { status: 'REJECTED' } });
    return { ok: true, rejected: r.count };
  }

  @Post('affiliates/bulk-delete')
  async bulkDelete(@Body() body: { ids?: string[] }) {
    const ids = body.ids ?? [];
    const r = await this.prisma.affiliate.deleteMany({ where: { id: { in: ids } } });
    return { ok: true, deleted: r.count };
  }

  @Post('affiliates/:id/reject')
  async reject(@Param('id') id: string) {
    await this.prisma.affiliate.update({ where: { id }, data: { status: 'REJECTED' } });
    return { ok: true };
  }

  // --- Affiliate detail + edit (coupons/commission/tags) ---
  @Get('affiliates/:id')
  async affiliateDetail(@Param('id') id: string) {
    const a = await this.prisma.affiliate.findUnique({
      where: { id },
      include: {
        group: { select: { id: true, name: true } },
        orders: { orderBy: { createdAt: 'desc' }, take: 10 },
        _count: { select: { orders: true, clicks: true } },
      },
    });
    if (!a) return null;
    return {
      id: a.id,
      name: a.name,
      email: a.email,
      socialHandle: a.socialHandle,
      website: a.website,
      status: a.status,
      paymentMethod: a.paymentMethod,
      paymentDetails: a.paymentDetails,
      couponCode: a.couponCode,
      couponDiscountType: a.couponDiscountType,
      couponDiscountValue: a.couponDiscountValue == null ? null : Number(a.couponDiscountValue),
      couponUsageLimitPerCustomer: a.couponUsageLimitPerCustomer,
      couponMaxRedemptions: a.couponMaxRedemptions,
      couponNewCustomersOnly: a.couponNewCustomersOnly,
      referralLink: a.referralLink,
      commissionType: a.commissionType,
      commissionValue: a.commissionValue == null ? null : Number(a.commissionValue),
      groupId: a.groupId,
      group: a.group,
      tags: a.tags,
      notes: a.notes,
      signupData: a.signupData || {},
      balance: Number(a.balance),
      clicks: a._count.clicks,
      sales: a._count.orders,
      orders: a.orders.map((o) => ({
        orderId: o.shopifyOrderId,
        date: o.createdAt.toISOString().slice(0, 10),
        total: Number(o.total),
        commission: Number(o.commission),
        status: o.status,
      })),
    };
  }

  @Post('affiliates')
  async createAffiliate(@Body() body: Record<string, any>) {
    const m = await getMerchant(this.prisma);
    if (!body.name?.trim() || !body.email?.trim()) {
      throw new BadRequestException('Name and email are required');
    }
    const exists = await this.prisma.affiliate.findFirst({ where: { merchantId: m.id, email: body.email } });
    if (exists) throw new BadRequestException('An affiliate with this email already exists');
    const status = body.status === 'ACTIVE' ? 'ACTIVE' : 'PENDING';
    const created = await this.prisma.affiliate.create({
      data: {
        merchantId: m.id,
        name: body.name,
        email: body.email,
        socialHandle: body.socialHandle || null,
        website: body.website || null,
        status,
        groupId: body.groupId || null,
        passwordHash: body.password ? hashPassword(body.password) : null,
        tags: [],
      },
    });
    if (status === 'ACTIVE') await this.approveAffiliate(created.id);
    return { ok: true, id: created.id };
  }

  @Delete('affiliates/:id')
  async deleteAffiliate(@Param('id') id: string) {
    await this.prisma.affiliate.delete({ where: { id } });
    return { ok: true };
  }

  @Put('affiliates/:id')
  async updateAffiliate(@Param('id') id: string, @Body() body: Record<string, any>) {
    const num = (v: any) => (v === '' || v === null || v === undefined ? null : Number(v));
    const data: any = {};
    // Profile
    if (body.name !== undefined) data.name = body.name;
    if (body.email !== undefined) data.email = body.email;
    if (body.socialHandle !== undefined) data.socialHandle = body.socialHandle || null;
    if (body.website !== undefined) data.website = body.website || null;
    if (body.status !== undefined) data.status = body.status;
    if (body.paymentMethod !== undefined) data.paymentMethod = body.paymentMethod || null;
    if (body.paymentDetails !== undefined) data.paymentDetails = (body.paymentDetails ?? null) as any;
    // Coupon
    if (body.couponCode !== undefined) data.couponCode = body.couponCode || null;
    if (body.couponDiscountType !== undefined) data.couponDiscountType = body.couponDiscountType || 'PERCENT';
    if (body.couponDiscountValue !== undefined) data.couponDiscountValue = num(body.couponDiscountValue);
    if (body.couponUsageLimitPerCustomer !== undefined)
      data.couponUsageLimitPerCustomer = num(body.couponUsageLimitPerCustomer);
    if (body.couponMaxRedemptions !== undefined)
      data.couponMaxRedemptions = num(body.couponMaxRedemptions);
    if (body.couponNewCustomersOnly !== undefined)
      data.couponNewCustomersOnly = !!body.couponNewCustomersOnly;
    if (body.commissionType !== undefined) data.commissionType = body.commissionType || null;
    if (body.commissionValue !== undefined) data.commissionValue = num(body.commissionValue);
    if (body.groupId !== undefined) data.groupId = body.groupId || null;
    if (body.notes !== undefined) data.notes = body.notes || null;
    if (body.tags !== undefined)
      data.tags = Array.isArray(body.tags)
        ? body.tags
        : String(body.tags)
            .split(',')
            .map((s) => s.trim())
            .filter(Boolean);
    const updated = await this.prisma.affiliate.update({ where: { id }, data });
    // If the affiliate's individual rate changed, push it into Marcadeo.
    if (body.commissionType !== undefined || body.commissionValue !== undefined) {
      const m = await getMerchant(this.prisma);
      await this.marcadeo.syncAffiliatePayout(m, updated);
    }
    return { ok: true };
  }
}
