// Cloudflare Pages Function — handles form submissions
// POST /submit

const LEAD_FIELDS = [
  'address', 'property_type', 'owns_land', 'listed', 'condition', 'why_selling',
  'timeline', 'asking_price', 'first_name', 'last_name', 'email', 'phone',
  'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term',
  'fbclid', 'gclid', 'page_url', 'referrer', 'sms_consent',
];

function normalize(data, request) {
  const out = {
    site_domain: new URL(request.url).hostname,
    created_at: new Date().toISOString(),
  };
  for (const f of LEAD_FIELDS) {
    out[f] = (data[f] == null ? '' : String(data[f]));
  }
  return out;
}

async function sendAlert(env, subject, message) {
  const apiKey = env.RESEND_API_KEY;
  if (!apiKey) return;
  const recipients = [];
  if (env.CLIENT_EMAIL && env.CLIENT_EMAIL.indexOf('@') > 0) {
    recipients.unshift(env.CLIENT_EMAIL);
  }
  if (!recipients.length) {
    console.error('Alert skipped: no recipient configured (set CLIENT_EMAIL or add to recipients).');
    return;
  }
  try {
    const res = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from: 'alerts@yourdomain.com',   // must be a domain verified in the owner's Resend account, or the send is rejected
        to: recipients,
        subject: subject,
        text: message,
      }),
    });
    if (!res.ok) {
      console.error('Alert send rejected: HTTP ' + res.status + ' ' + (await res.text()));
    }
  } catch (e) {
    console.error('Alert send failed:', e);
  }
}

function formatLeadEmail(siteName, payload, deliveryStatus, deliveryError, d1RowId, d1Status) {
  const fmt = function(v) { return v && v.length ? v : '(blank)'; };
  let backupLine;
  if (d1Status === 'ok') {
    backupLine = 'The lead is safely saved in your backup database. Please contact this lead manually using the info below.';
  } else if (d1Status === 'error') {
    backupLine = 'WARNING: the backup database write ALSO failed — this email is the only copy of this lead. Contact them using the info below.';
  } else {
    backupLine = 'WARNING: no backup database is bound — this email is the only copy of this lead. The D1 backup is a REQUIRED setup step (SETUP #3): create a D1 database and bind it as "db" on the Pages project, then redeploy.';
  }
  const lines = [
    'A new lead was submitted to your website but FAILED to deliver to your CRM via Zapier.',
    backupLine,
    '',
    'Submitted: ' + payload.created_at,
    'Site: ' + siteName,
  ];
  if (d1Status === 'ok') {
    lines.push('Backup ID: ' + (d1RowId != null ? d1RowId : '(unavailable)') + '  (reference this if you need the lead pulled from backup)');
  }
  lines.push(
    '',
    'CONTACT',
    '  Name:    ' + fmt(payload.first_name + ' ' + payload.last_name).trim(),
    '  Phone:   ' + fmt(payload.phone),
    '  Email:   ' + fmt(payload.email),
    '',
    'PROPERTY',
    '  Address:        ' + fmt(payload.address),
    '  Property type:  ' + fmt(payload.property_type),
    '  Owns land:      ' + fmt(payload.owns_land),
    '  Listed:         ' + fmt(payload.listed),
    '  Condition:      ' + fmt(payload.condition),
    '  Why selling:    ' + fmt(payload.why_selling),
    '  Timeline:       ' + fmt(payload.timeline),
    '  Asking price:   ' + fmt(payload.asking_price),
    '',
    'TRACKING',
    '  Source:    ' + fmt(payload.utm_source),
    '  Medium:    ' + fmt(payload.utm_medium),
    '  Campaign:  ' + fmt(payload.utm_campaign),
    '  Content:   ' + fmt(payload.utm_content),
    '  Term:      ' + fmt(payload.utm_term),
    '  fbclid:    ' + fmt(payload.fbclid),
    '  gclid:     ' + fmt(payload.gclid),
    '  Page:      ' + fmt(payload.page_url),
    '  Referrer:  ' + fmt(payload.referrer),
    '',
    'DELIVERY ERROR',
    '  Zapier status: ' + deliveryStatus,
    '  Error:         ' + (deliveryError || '(none)'),
    '  Action:        Check that your Zapier catch hook is enabled and reachable.',
  );
  return lines.join('\n');
}

export async function onRequestPost({ request, env }) {
  const headers = {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
  };

  const siteName = new URL(request.url).hostname;

  try {
    const data = await request.json();

    if (!data.first_name || !data.email || !data.phone) {
      return new Response(JSON.stringify({ error: 'Missing required fields' }), {
        status: 400,
        headers,
      });
    }

    const payload = normalize(data, request);
    const isHealthCheck = request.headers.get('X-Health-Check') === '1';
    // The D1 backup is REQUIRED (setup step, not optional). 'unbound' means the
    // site is misconfigured — the health check / smoke test fails on it.
    let d1Status = 'unbound';
    let d1RowId = null;
    let zapierStatus = 0;

    // 1. Write to Cloudflare D1 (required binding)
    if (env.db) {
      // One retry absorbs transient D1 storage resets (e.g. "storage caused object to be reset", seen 2026-07-03)
      let dbErr = null;
      for (let dbAttempt = 1; dbAttempt <= 2; dbAttempt++) {
      try {
        const d1Result = await env.db.prepare(`
          INSERT INTO submissions (
            address, property_type, owns_land, listed, condition, why_selling,
            timeline, asking_price, first_name, last_name, email, phone,
            utm_source, utm_medium, utm_campaign, utm_content, utm_term,
            fbclid, gclid, page_url, referrer, sms_consent
          ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        `).bind(
          payload.address, payload.property_type, payload.owns_land, payload.listed,
          payload.condition, payload.why_selling, payload.timeline, payload.asking_price,
          payload.first_name, payload.last_name, payload.email, payload.phone,
          payload.utm_source, payload.utm_medium, payload.utm_campaign, payload.utm_content,
          payload.utm_term, payload.fbclid, payload.gclid, payload.page_url, payload.referrer,
          payload.sms_consent
        ).run();
        d1Status = 'ok';
        d1RowId = d1Result && d1Result.meta ? d1Result.meta.last_row_id : null;
        dbErr = null;
        break;
      } catch (e) {
        dbErr = e;
        console.error('D1 write error (attempt ' + dbAttempt + '):', e);
        if (dbAttempt === 1) await new Promise(function (r) { setTimeout(r, 600); });
      }
      }
      if (dbErr) {
        d1Status = 'error';
        if (!isHealthCheck) await sendAlert(env,
          siteName + ': D1 write failed — ' + payload.first_name + ' ' + payload.last_name,
          'D1 write failed at ' + payload.created_at + '\n\n' +
          'Lead: ' + payload.first_name + ' ' + payload.last_name + '\n' +
          'Phone: ' + payload.phone + '\n' +
          'Email: ' + payload.email + '\n' +
          'Address: ' + payload.address + '\n\n' +
          'Error: ' + dbErr.message
        );
      }
    } else if (!isHealthCheck) {
      // Required binding is missing — nag on every lead until it's fixed (if Resend is on).
      await sendAlert(env,
        siteName + ': SITE MISCONFIGURED — required D1 backup database is not bound',
        'A lead was received but the REQUIRED backup database is not set up, so no backup copy was stored.\n\n' +
        'Lead: ' + payload.first_name + ' ' + payload.last_name + ' / ' + payload.phone + ' / ' + payload.email + '\n\n' +
        'Fix: create a D1 database, bind it to the Pages project as "db", run the SETUP #3 table command, then redeploy. The smoke test (test-form.sh) fails until this is done.'
      );
    }

    // 2. Fire Zapier webhook with normalized, complete schema (every field always present)
    // Retry once on failure; alert client + support if both attempts fail.
    const zapierUrl = env.ZAPIER_WEBHOOK_URL || '';
    if (zapierUrl) {
      let zapErrMsg = '';
      for (let attempt = 1; attempt <= 2; attempt++) {
        try {
          const zres = await fetch(zapierUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload),
          });
          zapierStatus = zres.status;
          if (zres.status >= 200 && zres.status < 300) { zapErrMsg = ''; break; }
          zapErrMsg = 'HTTP ' + zres.status;
        } catch (zapErr) {
          console.error('Zapier webhook error (attempt ' + attempt + '):', zapErr);
          zapierStatus = -1;
          zapErrMsg = zapErr.message || String(zapErr);
        }
      }
      if (zapErrMsg && !isHealthCheck) {
        await sendAlert(env,
          siteName + ': Lead received - Zapier delivery FAILED — ' + payload.first_name + ' ' + payload.last_name,
          formatLeadEmail(siteName, payload, zapierStatus, zapErrMsg, d1RowId, d1Status)
        );
      }
    } else if (!isHealthCheck) {
      // No CRM webhook configured at all — alert (if Resend is set up) so the lead isn't stranded silently.
      await sendAlert(env,
        siteName + ': Lead received but NO CRM webhook is configured — ' + payload.first_name + ' ' + payload.last_name,
        formatLeadEmail(siteName, payload, 'not configured', 'ZAPIER_WEBHOOK_URL is not set on the Pages project', d1RowId, d1Status)
      );
    }

    // 3. Fire any additional webhooks (optional)
    const extraWebhooks = env.EXTRA_WEBHOOK_URLS ? env.EXTRA_WEBHOOK_URLS.split(',') : [];
    for (const url of extraWebhooks) {
      if (url.trim()) {
        fetch(url.trim(), {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        }).catch(function(err) {
          console.error('Extra webhook error:', err);
        });
      }
    }

    // Total-loss guard: if the CRM didn't accept the lead AND no backup row was
    // written, the lead isn't stored anywhere — return an error so the visitor
    // sees the page's "something went wrong, try again" message instead of a
    // false success.
    const zapierOk = zapierStatus >= 200 && zapierStatus < 300;
    if (!zapierOk && d1Status !== 'ok') {
      return new Response(JSON.stringify({
        error: 'Delivery failed',
        zapier: zapierUrl ? zapierStatus : 'unconfigured',
        d1: d1Status,
      }), {
        status: 500,
        headers,
      });
    }

    const respBody = { success: true };
    if (isHealthCheck) {
      respBody.zapier = zapierUrl ? zapierStatus : 'unconfigured';
      respBody.d1 = d1Status;
    }
    return new Response(JSON.stringify(respBody), {
      status: 200,
      headers,
    });

  } catch (err) {
    console.error('Submit error:', err);
    await sendAlert(env,
      siteName + ': Submission crashed',
      'Form submission crashed at ' + new Date().toISOString() + '\n\n' +
      'Error: ' + err.message
    );
    return new Response(JSON.stringify({ error: 'Server error' }), {
      status: 500,
      headers,
    });
  }
}

export async function onRequestOptions() {
  return new Response(null, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    },
  });
}
