import { Router, Request, Response } from 'express';
import { authMiddleware } from '../middleware/auth';
import {
  createCheckoutSession,
  verifyTransaction,
  getPendingOrder,
  completePendingOrder,
  validateWebhookSignature,
  parseWebhookPayload,
} from '../services/authnet';
import { createPurchase } from '../models/purchase';
import { creditSinglePicks, creditDailyPass, creditMonthlyPass, findUserById, getPickBalance, markAffiliatePurchaseTracked, isInGracePeriod, computeExpiresAtUtc, TzSource } from '../models/user';
import { recordLedgerEntry } from '../models/ledger';
import { createTouristPass } from '../models/tourist-pass';
import { trackAffiliateConversion } from '../services/affiliate';
import { sendPurchaseConfirmationEmail } from '../services/email';
import { trackTikTokWebEvent } from '../services/tiktok';
import pool from '../db';

const router = Router();

const FRONTEND_URL = process.env.FRONTEND_URL || 'https://rainmakersports.app';

const PRODUCT_METADATA: Record<string, { contentName: string; value: number }> = {
  single_pick: { contentName: 'Single Forecast', value: 1 },
  daily_pass: { contentName: 'Today Only Tourist Pass', value: 4.99 },
  monthly_pass: { contentName: 'The Rain Man', value: 19.99 },
};

/** Extract client IP from request (respects X-Forwarded-For behind proxies) */
function getClientIp(req: Request): string {
  const xff = req.headers['x-forwarded-for'];
  if (typeof xff === 'string') return xff.split(',')[0].trim();
  if (Array.isArray(xff) && xff.length > 0) return xff[0].split(',')[0].trim();
  return req.socket?.remoteAddress || '';
}

/** Validate an IANA timezone string */
function isValidTimezone(tz: string): boolean {
  try {
    Intl.DateTimeFormat(undefined, { timeZone: tz });
    return true;
  } catch {
    return false;
  }
}

/** Resolve timezone from available sources */
function resolveTimezone(clientTz?: string): { timezone: string; source: TzSource } {
  if (clientTz && isValidTimezone(clientTz)) {
    return { timezone: clientTz, source: 'CLIENT' };
  }
  return { timezone: 'America/New_York', source: 'DEFAULT' };
}

function normalizeUrl(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const normalized = value.trim();
  if (!normalized) return null;
  if (/^https?:\/\//i.test(normalized)) return normalized;
  if (normalized.startsWith('/')) return `${FRONTEND_URL.replace(/\/$/, '')}${normalized}`;
  return null;
}

function getTikTokCookie(req: Request, key: string): string | null {
  const value = req.cookies?.[key];
  return typeof value === 'string' && value.trim() ? value.trim() : null;
}

function buildTikTokTrackingContext(req: Request, attribution?: Record<string, any> | null) {
  return {
    ip: getClientIp(req) || null,
    referrer: typeof req.headers.referer === 'string'
      ? req.headers.referer
      : typeof attribution?.referrer === 'string'
        ? attribution.referrer
        : null,
    ttclid: typeof attribution?.ttclid === 'string' ? attribution.ttclid : getTikTokCookie(req, 'rm_ttclid'),
    ttp: getTikTokCookie(req, '_ttp'),
    url: normalizeUrl(attribution?.full_path) || normalizeUrl(attribution?.url) || (typeof req.headers.referer === 'string' ? req.headers.referer : null),
    userAgent: typeof req.headers['user-agent'] === 'string' ? req.headers['user-agent'] : null,
  };
}

function getProductMetadata(productType: string, amountCents?: number) {
  const fallback = PRODUCT_METADATA[productType] || { contentName: productType, value: typeof amountCents === 'number' ? amountCents / 100 : 0 };
  return {
    contentName: fallback.contentName,
    value: typeof amountCents === 'number' ? amountCents / 100 : fallback.value,
  };
}

function trackTikTokPurchaseEvent(input: {
  email: string;
  event: 'InitiateCheckout' | 'Purchase';
  eventId: string;
  externalId: string;
  productType: string;
  amountCents?: number;
  ip?: string | null;
  referrer?: string | null;
  ttclid?: string | null;
  ttp?: string | null;
  url?: string | null;
  userAgent?: string | null;
}) {
  const product = getProductMetadata(input.productType, input.amountCents);
  return trackTikTokWebEvent({
    email: input.email,
    event: input.event,
    eventId: input.eventId,
    externalId: input.externalId,
    ip: input.ip || null,
    referrer: input.referrer || null,
    ttclid: input.ttclid || null,
    ttp: input.ttp || null,
    url: input.url || null,
    userAgent: input.userAgent || null,
    properties: {
      currency: 'USD',
      content_type: 'product',
      content_name: product.contentName,
      value: product.value,
      contents: [
        {
          content_id: input.productType,
          content_type: 'product',
          content_name: product.contentName,
        },
      ],
      description: input.event.toLowerCase(),
      status: 'completed',
    },
  });
}

// Helper to compute total balance
function computeTotal(balance: {
  single_picks: number;
  signup_bonus_forecasts?: number;
  survey_bonus_forecasts?: number;
  daily_pass_picks: number;
  daily_pass_valid: boolean;
  daily_free_forecasts: number;
}): number {
  return (balance.daily_free_forecasts || 0)
    + (balance.daily_pass_valid ? balance.daily_pass_picks : 0)
    + (balance.signup_bonus_forecasts || 0)
    + (balance.survey_bonus_forecasts || 0)
    + balance.single_picks;
}

type Queryable = {
  query: (text: string, params?: any[]) => Promise<{ rows: any[] }>;
};

type SettledPurchaseResult =
  | { status: 'credited' | 'already_processed'; userId: string; picksGranted: number; productType: string }
  | { status: 'missing_order' | 'forbidden' };

async function findPurchaseByInvoice(invoiceNumber: string, queryable: Queryable = pool): Promise<{
  id: string;
  user_id: string;
  product_type: string;
  picks_granted: number;
} | null> {
  const { rows } = await queryable.query(
    `SELECT id, user_id, product_type, picks_granted
     FROM rm_purchases
     WHERE stripe_session_id = $1
     LIMIT 1`,
    [invoiceNumber],
  );
  return rows[0] || null;
}

async function markPendingOrderCompleted(queryable: Queryable, invoiceNumber: string, transactionId: string): Promise<void> {
  await queryable.query(
    `UPDATE rm_pending_orders
     SET status = 'completed',
         transaction_id = COALESCE(NULLIF($1, ''), transaction_id),
         completed_at = COALESCE(completed_at, NOW())
     WHERE invoice_number = $2`,
    [transactionId || '', invoiceNumber],
  );
}

async function withPurchaseInvoiceLock<T>(invoiceNumber: string, fn: (client: any) => Promise<T>): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    await client.query('SELECT pg_advisory_xact_lock(hashtext($1))', [`purchase:${invoiceNumber}`]);
    const result = await fn(client);
    await client.query('COMMIT');
    return result;
  } catch (err) {
    await client.query('ROLLBACK').catch(() => {});
    throw err;
  } finally {
    client.release();
  }
}

async function settleAuthorizedPurchase(params: {
  invoiceNumber: string;
  transactionId: string;
  expectedUserId?: string;
  userAgent?: string | null;
}): Promise<SettledPurchaseResult> {
  return withPurchaseInvoiceLock(params.invoiceNumber, async (client) => {
    const { rows: pendingRows } = await client.query(
      'SELECT * FROM rm_pending_orders WHERE invoice_number = $1 LIMIT 1',
      [params.invoiceNumber],
    );
    const pendingOrder = pendingRows[0] || null;
    const existingPurchase = await findPurchaseByInvoice(params.invoiceNumber, client);
    const ownerUserId = existingPurchase?.user_id || pendingOrder?.user_id || null;

    if (params.expectedUserId && ownerUserId && ownerUserId !== params.expectedUserId) {
      return { status: 'forbidden' };
    }

    if (existingPurchase) {
      await markPendingOrderCompleted(client, params.invoiceNumber, params.transactionId);
      return {
        status: 'already_processed',
        userId: existingPurchase.user_id,
        picksGranted: existingPurchase.picks_granted,
        productType: existingPurchase.product_type,
      };
    }

    if (!pendingOrder) {
      return { status: 'missing_order' };
    }

    if (params.expectedUserId && pendingOrder.user_id !== params.expectedUserId) {
      return { status: 'forbidden' };
    }

    if (pendingOrder.status === 'completed') {
      return {
        status: 'already_processed',
        userId: pendingOrder.user_id,
        picksGranted: pendingOrder.picks_granted,
        productType: pendingOrder.product_type,
      };
    }

    await creditPurchase({
      userId: pendingOrder.user_id,
      productType: pendingOrder.product_type,
      picksGranted: pendingOrder.picks_granted,
      amountCents: pendingOrder.amount_cents,
      transactionId: params.transactionId,
      invoiceNumber: params.invoiceNumber,
      clientTimezone: pendingOrder.client_timezone,
      purchaseIp: pendingOrder.purchase_ip,
      userAgent: params.userAgent || null,
      attribution: pendingOrder.attribution || null,
    });

    return {
      status: 'credited',
      userId: pendingOrder.user_id,
      picksGranted: pendingOrder.picks_granted,
      productType: pendingOrder.product_type,
    };
  });
}

/**
 * Credit user account for a completed purchase.
 * Shared between webhook and verify endpoints for DRY.
 */
async function creditPurchase(params: {
  userId: string;
  productType: string;
  picksGranted: number;
  amountCents: number;
  transactionId: string;
  invoiceNumber: string;
  clientTimezone?: string;
  purchaseIp?: string;
  userAgent?: string | null;
  attribution?: any;
}): Promise<void> {
  const { userId, productType, picksGranted, amountCents, transactionId, invoiceNumber, clientTimezone, purchaseIp } = params;

  if (productType === 'monthly_pass') {
    await creditMonthlyPass(userId);
  } else if (productType === 'daily_pass') {
    const { timezone, source } = resolveTimezone(clientTimezone);
    const { expiresAtUtc } = await creditDailyPass(userId, picksGranted, timezone, source);

    await createTouristPass({
      userId,
      timezone,
      tzSource: source,
      expiresAtUtc,
      creditsGranted: picksGranted,
      purchaseIp: purchaseIp || null,
      userAgent: params.userAgent || null,
      stripeSessionId: invoiceNumber, // reuse column for authnet invoice ref
    });
  } else {
    await creditSinglePicks(userId, picksGranted);
  }

  await createPurchase({
    userId,
    productType,
    amountCents,
    picksGranted,
    stripePaymentIntentId: transactionId, // reuse column for authnet txn ID
    stripeSessionId: invoiceNumber,       // reuse column for authnet invoice
    attribution: params.attribution || null,
  });

  // Record ledger entry
  const balance = await getPickBalance(userId);
  const reason = productType === 'monthly_pass' ? 'PURCHASE_MONTHLY' : productType === 'daily_pass' ? 'PURCHASE_DAILY_PASS' : 'PURCHASE_SINGLE';
  await recordLedgerEntry(userId, picksGranted, reason as any, computeTotal(balance), {
    transactionId,
    invoiceNumber,
    productType,
    gateway: 'authorize.net',
    attribution: params.attribution || null,
  });

  console.log(`[purchase] ${productType} for user ${userId} (${picksGranted} forecasts, txn=${transactionId})`);

  const trackedUser = await findUserById(userId);
  if (trackedUser?.email) {
    void trackTikTokPurchaseEvent({
      email: trackedUser.email,
      event: 'Purchase',
      eventId: invoiceNumber || transactionId,
      externalId: userId,
      productType,
      amountCents,
      ip: purchaseIp || null,
      referrer: typeof params.attribution?.referrer === 'string' ? params.attribution.referrer : null,
      ttclid: typeof params.attribution?.ttclid === 'string' ? params.attribution.ttclid : null,
      ttp: typeof params.attribution?.ttp === 'string' ? params.attribution.ttp : null,
      url: normalizeUrl(params.attribution?.full_path) || normalizeUrl(params.attribution?.url),
      userAgent: params.userAgent || null,
    }).catch((err) => {
      console.error('[purchase] TikTok Purchase tracking failed (non-blocking):', err);
    });
  }

  // Send purchase confirmation email (non-blocking)
  try {
    const userForEmail = trackedUser || await findUserById(userId);
    if (userForEmail?.email) {
      const productNames: Record<string, string> = {
        single_pick: `${picksGranted} Forecast Credit${picksGranted !== 1 ? 's' : ''}`,
        daily_pass: 'Tourist Pass (Daily)',
        monthly_pass: 'Rain Man (Monthly)',
      };
      sendPurchaseConfirmationEmail(userForEmail.email, {
        productName: productNames[productType] || productType,
        picksGranted,
        totalBalance: computeTotal(balance),
      }).catch(err => console.error('[purchase] Confirmation email failed:', err));
    }
  } catch (emailErr) {
    console.error('[purchase] Confirmation email error (non-blocking):', emailErr);
  }

  // Affiliate purchase tracking (non-blocking)
  try {
    const user = await findUserById(userId);
    if (user?.affiliate_tracking_id && !user.affiliate_purchase_tracked_at) {
      const revenueDollars = amountCents / 100;
      await trackAffiliateConversion(user.affiliate_tracking_id, 'purchase', revenueDollars);
      await markAffiliatePurchaseTracked(userId);
      console.log(`[purchase] Affiliate tracked: user=${userId} revenue=$${revenueDollars}`);
    }
  } catch (affErr) {
    console.error('[purchase] Affiliate tracking failed (non-blocking):', affErr);
  }

  // Mark pending order as completed
  await completePendingOrder(invoiceNumber, transactionId);
}

// GET /api/purchase/entitlements — returns current user's forecast balance breakdown
router.get('/entitlements', authMiddleware, async (req: Request, res: Response) => {
  try {
    const balance = await getPickBalance(req.user!.userId);
    const user = await findUserById(req.user!.userId);
    const total = computeTotal(balance);

    const isRainMan = user?.is_weatherman || false;
    const plan = isRainMan ? 'RAINMAN' : balance.daily_pass_valid ? 'TOURIST' : 'NONE';

    res.json({
      plan,
      is_unlimited: isRainMan,
      forecastBalance: total,
      forecast_balance: total,
      monthlyPassExpiresAt: isRainMan ? (user?.monthly_pass_expires || null) : null,
      monthly_pass_expires_at: isRainMan ? (user?.monthly_pass_expires || null) : null,
      monthlyPassAutoRenews: false,
      free_forecasts_daily_limit: 9,
      free_forecasts_remaining_today: balance.daily_free_forecasts,
      tourist_pass_expires_at: balance.daily_pass_valid ? (balance.tourist_pass_expires_at || balance.next_reset_at) : null,
      tourist_pass_timezone: balance.tourist_pass_timezone || null,
      dailyFreeRemaining: balance.daily_free_forecasts,
      dailyFreeTotal: 9,
      dailyPassActive: balance.daily_pass_valid,
      dailyPassRemaining: balance.daily_pass_valid ? balance.daily_pass_picks : 0,
      singleCredits: balance.single_picks,
      totalAvailable: total,
      emailVerified: balance.email_verified,
      isRainMan,
      nextResetAt: balance.next_reset_at,
    });
  } catch (err) {
    console.error('Entitlements error:', err);
    res.status(500).json({ error: 'Failed to fetch entitlements' });
  }
});

// POST /api/purchase/checkout — create Authorize.Net hosted payment page
router.post('/checkout', authMiddleware, async (req: Request, res: Response) => {
  try {
    const { productType, clientTimezone, attribution } = req.body;
    if (!productType || !['single_pick', 'daily_pass', 'monthly_pass'].includes(productType)) {
      res.status(400).json({ error: 'Invalid product type. Use single_pick, daily_pass, or monthly_pass' });
      return;
    }

    const user = await findUserById(req.user!.userId);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    if (!user.email_verified && !user.is_weatherman && !isInGracePeriod(user)) {
      res.status(403).json({ error: 'Email verification required before purchasing', needsVerification: true });
      return;
    }

    const purchaseIp = getClientIp(req);

    const { token, formUrl } = await createCheckoutSession({
      userId: user.id,
      email: user.email,
      productType,
      successUrl: `${FRONTEND_URL}?purchase=success`,
      cancelUrl: `${FRONTEND_URL}?purchase=cancelled`,
      clientTimezone: clientTimezone || '',
      purchaseIp,
      attribution: attribution && typeof attribution === 'object' ? attribution : null,
    });

    const trackingContext = buildTikTokTrackingContext(req, attribution && typeof attribution === 'object' ? attribution : null);
    void trackTikTokPurchaseEvent({
      email: user.email,
      event: 'InitiateCheckout',
      eventId: `checkout_${user.id}_${productType}_${Date.now()}`,
      externalId: user.id,
      productType,
      amountCents: Math.round(getProductMetadata(productType).value * 100),
      ...trackingContext,
    }).catch((err) => {
      console.error('[purchase] TikTok InitiateCheckout tracking failed (non-blocking):', err);
    });

    // Return token + form URL — frontend will POST to the hosted form
    res.json({ token, formUrl });
  } catch (err) {
    console.error('Checkout error:', err);
    res.status(500).json({ error: 'Failed to create checkout session' });
  }
});

// POST /api/purchase/webhook — Authorize.Net Silent Post / Webhook notification
router.post('/webhook', async (req: Request, res: Response) => {
  try {
    const payload = parseWebhookPayload(req.body);
    if (!payload) {
      console.error('[webhook] Could not parse webhook payload');
      res.status(400).json({ error: 'Invalid webhook payload' });
      return;
    }

    // Validate signature — always required in production
    const xSignature = req.headers['x-anet-signature'] as string;
    if (!xSignature) {
      console.error('[webhook] Missing signature header — rejecting');
      res.status(401).json({ error: 'Missing signature' });
      return;
    }
    const valid = validateWebhookSignature(xSignature, payload.transactionId, payload.amount);
    if (!valid) {
      console.error('[webhook] Invalid signature');
      res.status(401).json({ error: 'Invalid signature' });
      return;
    }

    // Only process approved transactions (responseCode 1)
    if (payload.responseCode !== '1') {
      console.log(`[webhook] Skipping non-approved txn ${payload.transactionId} (code=${payload.responseCode})`);
      res.json({ received: true });
      return;
    }

    const settled = await settleAuthorizedPurchase({
      invoiceNumber: payload.invoiceNumber,
      transactionId: payload.transactionId,
    });
    if (settled.status === 'missing_order') {
      console.warn(`[webhook] No pending order found for invoice ${payload.invoiceNumber}`);
      res.json({ received: true });
      return;
    }

    res.json({ received: true });
  } catch (err) {
    console.error('[webhook] Error:', err);
    res.status(400).json({ error: 'Webhook processing failed' });
  }
});

// POST /api/purchase/verify — fallback verification after user returns from checkout
router.post('/verify', authMiddleware, async (req: Request, res: Response) => {
  try {
    const { transactionId, invoiceNumber } = req.body;

    // Support both: transactionId (from redirect params) or invoiceNumber (from pending order)
    let order: Awaited<ReturnType<typeof getPendingOrder>> = null;

    if (invoiceNumber) {
      order = await getPendingOrder(invoiceNumber);
    }

    // If we have a transactionId, verify with Authorize.Net
    if (transactionId) {
      const txnResult = await verifyTransaction(transactionId);
      if (!txnResult || !txnResult.paid) {
        res.json({ verified: false });
        return;
      }

      if (invoiceNumber && txnResult.invoiceNumber && txnResult.invoiceNumber !== invoiceNumber) {
        res.status(400).json({ verified: false, error: 'Invoice mismatch' });
        return;
      }

      const resolvedInvoiceNumber = txnResult.invoiceNumber || invoiceNumber || '';
      if (!resolvedInvoiceNumber) {
        res.json({ verified: false, error: 'Missing invoice number' });
        return;
      }

      // Look up order from invoice number in txn
      if (!order && txnResult.invoiceNumber) {
        order = await getPendingOrder(txnResult.invoiceNumber);
      }

      const existingPurchase = await findPurchaseByInvoice(resolvedInvoiceNumber);
      if (!order && existingPurchase) {
        if (existingPurchase.user_id !== req.user!.userId) {
          res.status(403).json({ error: 'Order does not belong to this user' });
          return;
        }
        res.json({ verified: true, picksGranted: existingPurchase.picks_granted, productType: existingPurchase.product_type });
        return;
      }

      if (!order) {
        res.json({ verified: false, error: 'No matching order found' });
        return;
      }

      const settled = await settleAuthorizedPurchase({
        invoiceNumber: resolvedInvoiceNumber,
        transactionId: txnResult.transactionId,
        expectedUserId: req.user!.userId,
        userAgent: req.headers['user-agent'] || null,
      });

      switch (settled.status) {
        case 'forbidden':
          res.status(403).json({ error: 'Order does not belong to this user' });
          return;
        case 'missing_order':
          res.json({ verified: false, error: 'No matching order found' });
          return;
        case 'credited':
        case 'already_processed':
          res.json({ verified: true, picksGranted: settled.picksGranted, productType: settled.productType });
          return;
      }
    }

    // If no transactionId but we have an invoiceNumber, check the most recent pending order for this user
    if (order && order.user_id === req.user!.userId) {
      if (order.status === 'completed') {
        const existingPurchase = invoiceNumber ? await findPurchaseByInvoice(invoiceNumber) : null;
        res.json({
          verified: true,
          picksGranted: existingPurchase?.picks_granted ?? order.picks_granted,
          productType: existingPurchase?.product_type ?? order.product_type,
        });
        return;
      }
    }

    // Try to find the most recent pending order for this user and verify it
    const { rows: recentOrders } = await pool.query(
      `SELECT * FROM rm_pending_orders WHERE user_id = $1 AND status = 'pending' ORDER BY created_at DESC LIMIT 1`,
      [req.user!.userId]
    );

    if (recentOrders.length > 0) {
      const recentOrder = recentOrders[0];
      const existingPurchase = await findPurchaseByInvoice(recentOrder.invoice_number);
      if (existingPurchase) {
        await completePendingOrder(recentOrder.invoice_number, '');
        res.json({
          verified: true,
          picksGranted: existingPurchase.picks_granted,
          productType: existingPurchase.product_type,
        });
        return;
      }
    }

    res.json({ verified: false });
  } catch (err) {
    console.error('Verify error:', err);
    res.status(500).json({ error: 'Verification failed' });
  }
});

export default router;
