import express from 'express';
import { rateLimit } from 'express-rate-limit';
import nodemailer from 'nodemailer';
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
import crypto from 'node:crypto';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { dirname, join } from 'node:path';

const root = dirname(fileURLToPath(import.meta.url));
const dist = join(root, 'dist');
const port = Number.parseInt(process.env.PORT || '4321', 10);
const isProduction = process.env.NODE_ENV === 'production';
const defaultContactEmail = 'kunthawat@moreminimore.com';
const mailConfigured = ['SES_ACCESS_KEY_ID', 'SES_SECRET_ACCESS_KEY', 'SES_REGION']
  .every((key) => Boolean(process.env[key]));
const fromEmail = process.env.SES_FROM_EMAIL || defaultContactEmail;
const toEmail = process.env.CONTACT_TO_EMAIL || defaultContactEmail;

const transporter = mailConfigured
  ? nodemailer.createTransport({
      SES: {
        sesClient: new SESv2Client({
          region: process.env.SES_REGION,
          credentials: {
            accessKeyId: process.env.SES_ACCESS_KEY_ID,
            secretAccessKey: process.env.SES_SECRET_ACCESS_KEY,
          },
        }),
        SendEmailCommand,
      },
    })
  : null;

export const app = express();
app.disable('x-powered-by');
const trustProxySetting = (process.env.TRUST_PROXY || '0').trim().toLowerCase();
if (!['0', 'false', ''].includes(trustProxySetting)) {
  const trustedHops = Number.parseInt(trustProxySetting, 10);
  if (!Number.isInteger(trustedHops) || trustedHops < 1 || trustedHops > 3 || String(trustedHops) !== trustProxySetting) {
    throw new Error('TRUST_PROXY must be 0, false, or an explicit proxy-hop count from 1 to 3');
  }
  app.set('trust proxy', trustedHops);
}
app.use((_, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  next();
});
app.use('/api', express.json({ limit: '12kb', strict: true }));

const contactLimiter = rateLimit({
  windowMs: 10 * 60 * 1000,
  limit: 6,
  standardHeaders: 'draft-8',
  legacyHeaders: false,
  message: { ok: false, error: 'กรุณารอสักครู่แล้วลองใหม่' },
});

const eventIds = new Map();
const EVENT_TTL_MS = 15 * 60 * 1000;
const EVENT_MAX = 5000;
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

export function claimEventId(eventId, now = Date.now()) {
  for (const [id, entry] of eventIds) {
    if (entry.expiresAt <= now) eventIds.delete(id);
  }
  if (!UUID_V4.test(eventId)) return 'invalid';
  const existing = eventIds.get(eventId);
  if (existing) return existing.status;
  while (eventIds.size >= EVENT_MAX) eventIds.delete(eventIds.keys().next().value);
  eventIds.set(eventId, { status: 'pending', expiresAt: now + EVENT_TTL_MS });
  return 'new';
}

function completeEventId(eventId, now = Date.now()) {
  const existing = eventIds.get(eventId);
  if (existing?.status === 'pending') {
    eventIds.set(eventId, { status: 'completed', expiresAt: now + EVENT_TTL_MS });
  }
}

function releaseEventId(eventId) {
  eventIds.delete(eventId);
}

function text(value, max) {
  return typeof value === 'string' ? value.trim().slice(0, max) : '';
}

function escapeHtml(value) {
  return value.replace(/[&<>"']/g, (character) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[character]);
}

function validateContact(body) {
  const name = text(body.name, 80);
  const contact = text(body.contact, 160);
  const problem = text(body.problem, 1500);
  const source = /^[a-z0-9-]{1,60}$/i.test(body.source || '') ? body.source : 'unknown';
  const eventId = text(body.eventId, 64);
  const fields = {};
  const email = contact.includes('@') ? contact : '';
  const phoneDigits = contact.replace(/\D/g, '');
  const tracking = body.tracking && typeof body.tracking === 'object' ? body.tracking : {};
  const consent = tracking.consent && typeof tracking.consent === 'object' ? tracking.consent : {};
  const analyticsConsent = consent.analytics === true;
  const marketingConsent = consent.marketing === true;

  if (name.length < 2) fields.name = 'กรุณากรอกชื่ออย่างน้อย 2 ตัวอักษร';
  if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) fields.contact = 'กรุณากรอกอีเมลให้ถูกต้อง';
  if (!email && (phoneDigits.length < 8 || phoneDigits.length > 15)) fields.contact = 'กรุณากรอกเบอร์โทรให้ถูกต้อง';
  if (problem.length < 5) fields.problem = 'กรุณาเล่าปัญหาอย่างน้อย 5 ตัวอักษร';

  const fbp = marketingConsent && /^fb\.\d\.\d{10,16}\.[A-Za-z0-9._-]{1,120}$/.test(tracking.fbp || '') ? tracking.fbp : '';
  const fbc = marketingConsent && /^fb\.\d\.\d{10,16}\.[A-Za-z0-9._-]{1,160}$/.test(tracking.fbc || '') ? tracking.fbc : '';
  const gaClientId = analyticsConsent && /^\d{1,20}\.\d{1,20}$/.test(tracking.gaClientId || '') ? tracking.gaClientId : '';
  const gaSessionId = analyticsConsent && /^\d{6,20}$/.test(tracking.gaSessionId || '') ? tracking.gaSessionId : '';

  return {
    fields,
    validEventId: UUID_V4.test(eventId),
    values: {
      name, contact, problem, source, eventId, email, phoneDigits,
      consent: { analytics: analyticsConsent, marketing: marketingConsent },
      fbp, fbc, gaClientId, gaSessionId,
    },
  };
}

function sha256(value) {
  return crypto.createHash('sha256').update(value).digest('hex');
}

function normalizedNameParts(name) {
  const parts = name.normalize('NFKC').trim().toLowerCase().split(/\s+/).filter(Boolean);
  return { first: parts[0] || '', last: parts.length > 1 ? parts[parts.length - 1] : '' };
}

export function normalizeThaiPhone(value) {
  const digits = String(value || '').replace(/\D/g, '');
  if (/^0\d{8,9}$/.test(digits)) return `66${digits.slice(1)}`;
  if (/^66\d{8,9}$/.test(digits)) return digits;
  return '';
}

function sourceUrl(source) {
  if (source === 'home') return 'https://moreminimore.com/';
  if (/^(website-development|marketing-consult|automation-workflow|ai-consult)$/.test(source)) {
    return `https://moreminimore.com/services/${source}/`;
  }
  return 'https://moreminimore.com/';
}

async function providerPost(url, body, fetchImpl) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 2200);
  try {
    const response = await fetchImpl(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
      signal: controller.signal,
    });
    if (!response.ok) throw new Error('provider_request_failed');
  } finally {
    clearTimeout(timeout);
  }
}

export async function dispatchProviderConversions(values, requestContext, env = process.env, fetchImpl = globalThis.fetch) {
  const tasks = [];
  const eventTime = Math.floor(Date.now() / 1000);

  if (values.consent.marketing && env.META_ACCESS_TOKEN) {
    const names = normalizedNameParts(values.name);
    const normalizedPhone = normalizeThaiPhone(values.phoneDigits);
    const userData = {
      client_ip_address: requestContext.ip,
      client_user_agent: requestContext.userAgent,
      ...(values.email ? { em: [sha256(values.email.normalize('NFKC').trim().toLowerCase())] } : {}),
      ...(!values.email && normalizedPhone ? { ph: [sha256(normalizedPhone)] } : {}),
      ...(names.first ? { fn: [sha256(names.first)] } : {}),
      ...(names.last ? { ln: [sha256(names.last)] } : {}),
      ...(values.fbp ? { fbp: values.fbp } : {}),
      ...(values.fbc ? { fbc: values.fbc } : {}),
    };
    const metaPixelId = env.META_PIXEL_ID || '418349260078648';
    const metaUrl = `https://graph.facebook.com/v22.0/${encodeURIComponent(metaPixelId)}/events?access_token=${encodeURIComponent(env.META_ACCESS_TOKEN)}`;
    tasks.push(providerPost(metaUrl, {
      data: [{
        event_name: 'Lead',
        event_time: eventTime,
        event_source_url: sourceUrl(values.source),
        action_source: 'website',
        event_id: values.eventId,
        user_data: userData,
        custom_data: { form_location: values.source },
      }],
    }, fetchImpl));
  }

  if (values.consent.analytics && env.GA4_API_SECRET && values.gaClientId) {
    const measurementId = env.GA4_MEASUREMENT_ID || 'G-74BHREDLC3';
    const gaUrl = `https://www.google-analytics.com/mp/collect?measurement_id=${encodeURIComponent(measurementId)}&api_secret=${encodeURIComponent(env.GA4_API_SECRET)}`;
    tasks.push(providerPost(gaUrl, {
      client_id: values.gaClientId,
      consent: {
        ad_user_data: values.consent.marketing ? 'GRANTED' : 'DENIED',
        ad_personalization: values.consent.marketing ? 'GRANTED' : 'DENIED',
      },
      events: [{
        name: 'form_submitted',
        params: {
          ...(values.gaSessionId ? { session_id: values.gaSessionId } : {}),
          engagement_time_msec: 100,
          form_location: values.source,
          event_id: values.eventId,
        },
      }],
    }, fetchImpl));
  }

  return Promise.allSettled(tasks);
}

function isSameOriginRequest(req) {
  if (req.get('sec-fetch-site') === 'cross-site') return false;
  const origin = req.get('origin');
  if (!origin) return true;
  try {
    return new URL(origin).host === req.get('host');
  } catch {
    return false;
  }
}

app.get('/api/health', (_, res) => {
  res.setHeader('Cache-Control', 'no-store');
  res.json({ ok: true });
});

const permanentRedirects = new Map([
  ['/portfolio', '/#portfolio'], ['/portfolio/', '/#portfolio'],
  ['/contact', '/#contact'], ['/contact/', '/#contact'],
  ['/sitemap.xml', '/sitemap-index.xml'],
  ['/about', '/about/'], ['/services', '/services/'], ['/faq', '/faq/'],
  ['/privacy', '/privacy/'], ['/terms', '/terms/'], ['/blog', '/blog/'],
  ...['website-development', 'marketing-consult', 'automation-workflow', 'ai-consult'].map((slug) => [`/services/${slug}`, `/services/${slug}/`]),
  ...[
    '2026-06-15-chatgpt-opens-ads-for-all',
    '2026-06-26-ai-visibility-operational-alignment',
    '2026-06-26-b2b-marketers-future-of-work',
    '2026-06-26-brand-reputation-precedes-you-with-ai',
    '2026-06-26-fintech-invisible-to-ai-agents',
    '2026-06-26-google-seo-for-ai-agents',
    '2026-06-26-two-things-b2b-marketers-should-do-with-ai',
    '2026-06-27-ai-brand-reputation-167k-citations',
    '2026-06-27-ai-recommendation-multi-industry-map',
    '2026-06-27-google-open-knowledge-format',
    '2026-06-27-opid-ai-agent-self-training',
    '2026-06-28-broken-form-cost-agency-leads',
    '2026-06-28-openknowledge-ai-first-obsidian',
    '2026-06-28-profound-vs-bluefish-aeo',
    '2026-07-01-chatgpt-reasoning-modes-overlap',
    '2026-07-01-chatgpt-source-selection',
    '2026-07-01-deepseek-dspark-devin-fusion',
  ].map((slug) => [`/blog/${slug}`, `/blog/${slug}/`]),
]);

app.use((req, res, next) => {
  if (req.method !== 'GET' && req.method !== 'HEAD') return next();
  const destination = permanentRedirects.get(req.path);
  if (!destination) return next();
  return res.redirect(301, destination);
});

app.post('/api/contact', contactLimiter, async (req, res) => {
  res.setHeader('Cache-Control', 'no-store');
  if (!isSameOriginRequest(req)) return res.status(403).json({ ok: false, error: 'ไม่สามารถส่งข้อมูลได้' });
  if (!req.is('application/json')) return res.status(415).json({ ok: false, error: 'ไม่สามารถส่งข้อมูลได้' });
  if (text(req.body?.website, 200)) return res.json({ ok: true });

  const { fields, validEventId, values } = validateContact(req.body || {});
  if (Object.keys(fields).length) return res.status(400).json({ ok: false, error: 'กรุณาตรวจสอบข้อมูล', fields });
  if (!validEventId) return res.status(400).json({ ok: false, error: 'ข้อมูลการส่งไม่ถูกต้อง' });
  const eventState = claimEventId(values.eventId);
  if (eventState === 'completed') return res.json({ ok: true, duplicate: true });
  if (eventState === 'pending') return res.status(409).json({ ok: false, processing: true, error: 'ระบบกำลังดำเนินการคำขอนี้' });
  if (eventState !== 'new') return res.status(400).json({ ok: false, error: 'ข้อมูลการส่งไม่ถูกต้อง' });

  if (!transporter) {
    if (!isProduction) {
      completeEventId(values.eventId);
      console.info('[api/contact] Development submission accepted; email delivery is disabled');
      return res.json({ ok: true, devMode: true });
    }
    releaseEventId(values.eventId);
    console.error('[api/contact] Email delivery is not configured');
    return res.status(503).json({ ok: false, error: 'ระบบยังไม่พร้อม กรุณาลองใหม่ภายหลัง' });
  }

  try {
    const subject = 'ข้อความติดต่อใหม่จากเว็บไซต์ MoreminiMore';
    const plain = [
      'ข้อความติดต่อใหม่จากเว็บไซต์ MoreminiMore', '',
      `ชื่อ: ${values.name}`,
      `เบอร์โทรหรืออีเมล: ${values.contact}`,
      `ตอนนี้ติดเรื่องอะไร: ${values.problem}`,
      `หน้าที่ส่ง: ${values.source}`,
    ].join('\n');
    const html = `
      <h2>ข้อความติดต่อใหม่จากเว็บไซต์ MoreminiMore</h2>
      <table cellpadding="8" cellspacing="0" border="0">
        <tr><th align="left">ชื่อ</th><td>${escapeHtml(values.name)}</td></tr>
        <tr><th align="left">เบอร์โทรหรืออีเมล</th><td>${escapeHtml(values.contact)}</td></tr>
        <tr><th align="left">ตอนนี้ติดเรื่องอะไร</th><td>${escapeHtml(values.problem).replace(/\n/g, '<br>')}</td></tr>
        <tr><th align="left">หน้าที่ส่ง</th><td>${escapeHtml(values.source)}</td></tr>
      </table>`;

    await transporter.sendMail({
      from: `MoreminiMore <${fromEmail}>`,
      to: toEmail,
      replyTo: values.email || undefined,
      subject, text: plain, html,
    });

    completeEventId(values.eventId);
    await dispatchProviderConversions(values, {
      ip: req.ip || '',
      userAgent: text(req.get('user-agent'), 300),
    });
    console.info('[api/contact] Message delivered');
    return res.json({ ok: true });
  } catch {
    releaseEventId(values.eventId);
    console.error('[api/contact] Email delivery failed');
    return res.status(500).json({ ok: false, error: 'ส่งไม่สำเร็จ กรุณาลองใหม่ภายหลัง' });
  }
});

app.use(express.static(dist, {
  extensions: ['html'],
  setHeaders(res, filePath) {
    if (filePath.endsWith('.html')) res.setHeader('Cache-Control', 'no-cache');
    else res.setHeader('Cache-Control', 'public, max-age=604800, immutable');
  },
}));

app.use((_, res) => {
  res.setHeader('Cache-Control', 'no-cache');
  res.status(404).sendFile(join(dist, '404.html'));
});

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  app.listen(port, '0.0.0.0', () => console.info(`[server] Listening on port ${port}`));
}
