import { Body, Controller, Get, Ip, Param, Post, Query, Res } from '@nestjs/common';
import type { Response } from 'express';
import { PrismaService } from '../common/prisma.service';
import { calcCommission, getMerchant } from '../common/core';

@Controller()
export class TrackingController {
  constructor(private prisma: PrismaService) {}

  /** Resolve a short-link code -> its target URL (used by the /s/:code page). */
  @Get('shortlink/:code')
  async resolveShort(@Param('code') code: string) {
    const link = await this.prisma.shortLink.findUnique({ where: { code } });
    if (!link) return { url: null };
    await this.prisma.shortLink.update({ where: { code }, data: { hits: { increment: 1 } } });
    return { url: link.targetUrl };
  }

  /**
   * Referral link. An affiliate shares .../api/r/SARA47
   * -> record a click, then bounce the visitor to the store with ?ref=code.
   */
  @Get('r/:code')
  async referral(
    @Param('code') code: string,
    @Query('to') to: string | undefined,
    @Res() res: Response,
  ) {
    const affiliate = await this.prisma.affiliate.findFirst({
      where: { couponCode: { equals: code, mode: 'insensitive' }, status: 'ACTIVE' },
    });
    if (affiliate) {
      await this.prisma.click.create({ data: { affiliateId: affiliate.id } });
    }
    // Product links pass ?to=<destination>; otherwise fall back to the demo store.
    const web = process.env.WEB_URL || 'http://localhost:3000';
    const dest = to && /^https?:\/\//.test(to) ? to : `${web}/store?ref=${encodeURIComponent(code)}`;
    return res.redirect(dest);
  }

  /**
   * The conversion. In production this is a Shopify orders/create webhook;
   * here the demo store posts the purchase so the whole loop is clickable.
   *
   * Attribution: match `code` to an affiliate's coupon/referral code, compute
   * commission per program rules, record the order, credit the affiliate.
   */
  @Post('checkout/simulate')
  async simulate(
    @Ip() ip: string,
    @Body()
    body: {
      code?: string;
      amount?: number;
      via?: 'click' | 'coupon';
      customerEmail?: string;
    },
  ) {
    const merchant = await getMerchant(this.prisma);
    const amount = Number(body.amount) || 0;
    if (amount <= 0) return { attributed: false, reason: 'Invalid amount' };

    if (!body.code) {
      return { attributed: false, reason: 'No referral or coupon on this order' };
    }

    const affiliate = await this.prisma.affiliate.findFirst({
      where: {
        merchantId: merchant.id,
        couponCode: { equals: body.code, mode: 'insensitive' },
        status: 'ACTIVE',
      },
      include: { group: true },
    });

    if (!affiliate) {
      return { attributed: false, reason: `No active affiliate for code "${body.code}"` };
    }

    // Sales so far this month drive tiered group commission.
    const now = new Date();
    const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
    const salesThisMonth = await this.prisma.referralOrder.count({
      where: { affiliateId: affiliate.id, status: 'APPROVED', createdAt: { gte: monthStart } },
    });

    // Commission uses subtotal == total here (no tax/shipping in the demo).
    const { commission, source, type, value } = calcCommission({
      merchant,
      affiliate,
      subtotal: amount,
      total: amount,
      salesThisMonth,
    });

    const order = await this.prisma.referralOrder.create({
      data: {
        merchantId: merchant.id,
        affiliateId: affiliate.id,
        shopifyOrderId: `${Date.now()}`,
        attribution: body.via || 'coupon',
        customerEmail: body.customerEmail || null,
        ip: ip || null,
        subtotal: amount,
        total: amount,
        commission,
        status: 'APPROVED',
      },
    });

    const updated = await this.prisma.affiliate.update({
      where: { id: affiliate.id },
      data: { balance: { increment: commission } },
    });

    // Sale notification email (PRD 4.9) — respects merchant + affiliate toggles.
    const mNotif = (merchant.notifications as Record<string, boolean>) || {};
    const aPref = (affiliate.notifyPrefs as Record<string, boolean>) || {};
    if (mNotif.sale_notification !== false && aPref.sale !== false) {
      await this.prisma.emailLog.create({
        data: {
          merchantId: merchant.id,
          toEmail: affiliate.email,
          type: 'sale_notification',
          subject: `New sale — you earned ₹${commission}`,
        },
      });
    }

    // --- Fraud checks (PRD 4.4) ---
    // 1) Self-referral: the buyer's email is the affiliate's own email.
    if (
      body.customerEmail &&
      body.customerEmail.toLowerCase() === affiliate.email.toLowerCase()
    ) {
      await this.prisma.fraudFlag.create({
        data: {
          merchantId: merchant.id,
          affiliateId: affiliate.id,
          orderId: order.id,
          reason: 'self_referral',
          ipAddress: ip || null,
        },
      });
    }

    // 2) Same-IP burst: 3+ orders from this IP within 10 minutes.
    if (ip) {
      const tenMinAgo = new Date(now.getTime() - 10 * 60 * 1000);
      const sameIpCount = await this.prisma.referralOrder.count({
        where: { merchantId: merchant.id, ip, createdAt: { gte: tenMinAgo } },
      });
      if (sameIpCount >= 3) {
        await this.prisma.fraudFlag.create({
          data: {
            merchantId: merchant.id,
            affiliateId: affiliate.id,
            orderId: order.id,
            reason: 'same_ip_repeat',
            ipAddress: ip,
          },
        });
      }
    }

    return {
      attributed: true,
      affiliate: { id: affiliate.id, name: affiliate.name },
      orderId: order.shopifyOrderId,
      amount,
      commission,
      rule: `${source} ${type === 'PERCENT' ? value + '%' : '₹' + value}`,
      newBalance: Number(updated.balance),
    };
  }

  /**
   * Multi-campaign checkout — ONE store, MULTIPLE campaigns/brands.
   * An order's line-items are split by brand (Shopify vendor), each brand's
   * subtotal is attributed to its own Campaign with that campaign's commission
   * rate. Proves one merchant can run many campaigns in a single store.
   */
  @Post('checkout/multi')
  async multiCheckout(
    @Ip() ip: string,
    @Body()
    body: {
      code?: string;
      customerEmail?: string;
      lineItems?: { vendor?: string; title?: string; price?: number; qty?: number }[];
    },
  ) {
    const merchant = await getMerchant(this.prisma);
    if (!body.code) return { attributed: false, reason: 'No coupon on this order' };
    const items = (body.lineItems || []).filter((i) => Number(i.price) > 0);
    if (!items.length) return { attributed: false, reason: 'No line items' };

    const affiliate = await this.prisma.affiliate.findFirst({
      where: { merchantId: merchant.id, couponCode: { equals: body.code, mode: 'insensitive' }, status: 'ACTIVE' },
    });
    if (!affiliate) return { attributed: false, reason: `No active affiliate for code "${body.code}"` };

    const campaigns = await this.prisma.campaign.findMany({ where: { merchantId: merchant.id, active: true } });

    // Resolve each line-item to a campaign (by vendor/tag/product), then group.
    const resolve = (item: { vendor?: string; title?: string }) =>
      campaigns.find((c) => {
        const v = c.matchValue.toLowerCase();
        if (c.matchType === 'ALL') return true;
        if (c.matchType === 'VENDOR') return (item.vendor || '').toLowerCase() === v;
        if (c.matchType === 'PRODUCT') return (item.title || '').toLowerCase().includes(v);
        return false;
      });

    const byCampaign = new Map<string, number>();
    let unmatched = 0;
    for (const it of items) {
      const line = Number(it.price) * (Number(it.qty) || 1);
      const c = resolve(it);
      if (!c) {
        unmatched += line;
        continue;
      }
      byCampaign.set(c.id, (byCampaign.get(c.id) || 0) + line);
    }

    const baseOrderId = `${Date.now()}`;
    const results: any[] = [];
    for (const [campaignId, subtotal] of byCampaign) {
      const c = campaigns.find((x) => x.id === campaignId)!;
      const rate = Number(c.commissionValue);
      const commission = c.commissionType === 'FLAT' ? rate : (subtotal * rate) / 100;
      await this.prisma.referralOrder.create({
        data: {
          merchantId: merchant.id,
          affiliateId: affiliate.id,
          campaignId: c.id,
          shopifyOrderId: baseOrderId, // same order, split per campaign (unique incl. campaignId)
          attribution: 'coupon',
          customerEmail: body.customerEmail || null,
          ip: ip || null,
          subtotal,
          total: subtotal,
          commission,
          status: 'APPROVED',
        },
      });
      await this.prisma.affiliate.update({ where: { id: affiliate.id }, data: { balance: { increment: commission } } });
      results.push({
        campaign: c.name,
        matchedBy: `${c.matchType}=${c.matchValue}`,
        subtotal,
        rate: c.commissionType === 'FLAT' ? `₹${rate}` : `${rate}%`,
        commission,
      });
    }

    const updated = await this.prisma.affiliate.findUnique({ where: { id: affiliate.id } });
    return {
      attributed: results.length > 0,
      orderId: baseOrderId,
      affiliate: { id: affiliate.id, name: affiliate.name, coupon: affiliate.couponCode },
      campaigns: results,
      unmatchedAmount: unmatched,
      totalCommission: results.reduce((s, r) => s + r.commission, 0),
      newBalance: updated ? Number(updated.balance) : null,
    };
  }
}
