import crypto from 'crypto';
import { getPool } from './db.js';
import type { RowDataPacket } from 'mysql2';

const TOKEN_RE = /^[A-Za-z0-9_-]{32}$/;
const BOT_RE = /bot|crawl|spider|slurp|facebookexternalhit/i;

export interface TrackingLink {
  token: string;
  customerId: string;
  draftId: number | null;
  destinationUrl: string;
  clickCount: number;
  expiresAt: Date | null;
  createdAt: Date;
}

interface TrackingLinkRow extends RowDataPacket {
  token: string;
  customer_id: string;
  draft_id: number | null;
  destination_url: string;
  click_count: number;
  expires_at: Date | null;
  created_at: Date;
}

export function isValidTokenFormat(token: string): boolean {
  return TOKEN_RE.test(token);
}

export function isBot(userAgent: string): boolean {
  return BOT_RE.test(userAgent);
}

export function validateDestinationUrl(url: string): void {
  if (url.length > 2048) throw new Error('Destination URL too long');
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error('Invalid destination URL');
  }
  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new Error('Destination URL must use http or https');
  }
  if (parsed.username || parsed.password) {
    throw new Error('Destination URL must not contain credentials');
  }
}

export async function createTrackingLink(params: {
  customerId: string;
  draftId: number | null;
  destinationUrl: string;
  expiresAt: Date | null;
}): Promise<string> {
  validateDestinationUrl(params.destinationUrl);

  for (let attempt = 0; attempt < 3; attempt++) {
    const token = crypto.randomBytes(24).toString('base64url');
    try {
      await getPool().query(
        `INSERT INTO tracking_links (token, customer_id, draft_id, destination_url, expires_at)
         VALUES (?, ?, ?, ?, ?)`,
        [token, params.customerId, params.draftId ?? null, params.destinationUrl, params.expiresAt]
      );
      return token;
    } catch (err: unknown) {
      const mysqlErr = err as { code?: string };
      if (mysqlErr.code === 'ER_DUP_ENTRY' && attempt < 2) continue;
      throw err;
    }
  }
  throw new Error('Failed to generate unique tracking token after 3 attempts');
}

export async function getTrackingLink(token: string): Promise<TrackingLink | null> {
  const [rows] = await getPool().query<TrackingLinkRow[]>(
    'SELECT token, customer_id, draft_id, destination_url, click_count, expires_at, created_at FROM tracking_links WHERE token = ?',
    [token]
  );
  const row = rows[0];
  if (!row) return null;
  if (row.expires_at && row.expires_at < new Date()) return null;
  return {
    token: row.token,
    customerId: row.customer_id,
    draftId: row.draft_id,
    destinationUrl: row.destination_url,
    clickCount: Number(row.click_count),
    expiresAt: row.expires_at,
    createdAt: row.created_at,
  };
}

// Fire-and-forget — must be called AFTER res.redirect() is sent
export function recordClick(token: string): void {
  getPool()
    .query('UPDATE tracking_links SET click_count = click_count + 1 WHERE token = ?', [token])
    .catch(err => console.error('[tracking] click record failed:', err));
}
