<?php
/**
 * Contact form handler for newnorfolkroofingltd.co.uk
 *
 * Accepts a POST from the site's callback forms, sends the enquiry over SMTP,
 * and replies with JSON (for the fetch-based submit) or a redirect (no-JS fallback).
 */

declare(strict_types=1);

// Never print PHP notices into the response body — a stray warning would corrupt
// the JSON and surface to the visitor as a generic "something went wrong".
ini_set('display_errors', '0');
error_reporting(E_ALL);

// If PHP dies unexpectedly, still answer with JSON so the cause is visible in the
// browser's network tab rather than an opaque HTML error page.
register_shutdown_function(static function () {
    $fatal = error_get_last();
    if (!$fatal || !in_array($fatal['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
        return;
    }
    if (headers_sent()) {
        return;
    }
    http_response_code(500);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'ok' => false,
        'message' => 'Server error while sending. Please call us on 07905 184 968.',
        'detail' => $fatal['message'] . ' (' . basename($fatal['file']) . ':' . $fatal['line'] . ')',
    ]);
});

foreach (['Exception', 'PHPMailer', 'SMTP'] as $lib) {
    $path = __DIR__ . '/lib/PHPMailer/' . $lib . '.php';
    if (!is_readable($path)) {
        http_response_code(500);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode([
            'ok' => false,
            'message' => 'Mail library missing. Please re-upload the lib folder.',
            'detail' => 'Not readable: ' . $path,
        ]);
        exit;
    }
    require $path;
}

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

const MAX_FIELD_LENGTH = 5000;
const THROTTLE_SECONDS = 20;

/** Whether the browser asked for JSON (our fetch submit sets this). */
function wantsJson(): bool
{
    $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
    $xhr = $_SERVER['HTTP_X_REQUESTED_WITH'] ?? '';
    return strpos($accept, 'application/json') !== false || $xhr === 'fetch';
}

/** Sends the response and exits. Kept PHP 7.4-compatible (no `never` return type). */
function respond(bool $ok, string $message, int $status = 200)
{
    if (wantsJson()) {
        http_response_code($status);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['ok' => $ok, 'message' => $message]);
        exit;
    }

    // No-JS fallback: success goes to the thank-you page, errors render inline
    // here so the visitor always sees why the submission did not go through.
    if ($ok) {
        http_response_code(303);
        header('Location: /thank-you/');
        exit;
    }

    http_response_code($status);
    header('Content-Type: text/html; charset=utf-8');
    $safe = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
    echo <<<HTML
    <!doctype html>
    <html lang="en-GB">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="robots" content="noindex">
    <title>Enquiry not sent — New Norfolk Roofing Ltd</title>
    <style>
      body{margin:0;font:400 17px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
      color:#5f6b76;background:#f5f7f8;display:grid;place-items:center;min-height:100vh;padding:24px;}
      .box{background:#fff;border:1px solid #e2e6ea;border-left:4px solid #c0392b;border-radius:4px;
      padding:34px;max-width:520px;box-shadow:0 2px 14px rgba(35,40,45,.08);}
      h1{font:700 22px/1.3 Georgia,serif;color:#23282d;margin:0 0 12px;}
      a.btn{display:inline-block;margin-top:20px;background:#5cb847;color:#fff;text-decoration:none;
      font-weight:700;padding:13px 26px;border-radius:4px;}
    </style>
    </head>
    <body>
      <div class="box">
        <h1>Your enquiry was not sent</h1>
        <p>{$safe}</p>
        <a class="btn" href="/contact/">Back to the contact page</a>
      </div>
    </body>
    </html>
    HTML;
    exit;
}

/** Trim, cap length, and strip control characters that could forge headers. */
function cleanField(string $key): string
{
    $value = trim((string) ($_POST[$key] ?? ''));
    $value = substr($value, 0, MAX_FIELD_LENGTH);
    return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value) ?? '';
}

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    respond(false, 'Method not allowed.', 405);
}

$configPath = __DIR__ . '/mail-config.php';
if (!is_readable($configPath)) {
    error_log('mail.php: mail-config.php is missing. Copy mail-config.sample.php and add the password.');
    respond(false, 'The contact form is not configured yet. Please call us instead.', 500);
}
$config = require $configPath;

$transport = $config['transport'] ?? 'smtp';

if ($transport === 'smtp' && empty($config['smtp_password'])) {
    error_log('mail.php: smtp_password is empty in mail-config.php.');
    respond(false, 'The contact form is not configured yet. Please call us instead.', 500);
}

// Bots fill hidden fields that humans never see.
if (cleanField('website') !== '') {
    respond(true, 'Thank you — we will be in touch shortly.');
}

// Reject submissions completed implausibly fast, and rate-limit per session.
session_start();
$now = time();
$last = $_SESSION['last_enquiry_at'] ?? 0;
if ($now - (int) $last < THROTTLE_SECONDS) {
    respond(false, 'You have just sent an enquiry. Please wait a moment before sending another.', 429);
}

$name = cleanField('name');
$phone = cleanField('phone');
$email = cleanField('email');
$message = cleanField('message');
$pageSource = cleanField('source') ?: 'Website';

$errors = [];
if ($name === '') {
    $errors[] = 'name';
}
if ($phone === '') {
    $errors[] = 'phone number';
}
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = 'a valid email address';
}

if ($errors) {
    respond(false, 'Please provide ' . implode(', ', $errors) . '.', 422);
}

$rows = [
    'Name' => $name,
    'Phone' => $phone,
    'Email' => $email,
    'Message' => $message !== '' ? $message : '(none supplied)',
    'Form' => $pageSource,
    'Submitted' => date('j M Y'),
    'IP address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
];

$textLines = [];
$htmlRows = '';
foreach ($rows as $label => $value) {
    $textLines[] = $label . ': ' . $value;
    $htmlRows .= '<tr>'
        . '<th align="left" style="padding:8px 16px 8px 0;vertical-align:top;color:#5f6b76;'
        . 'font:700 13px Arial,sans-serif;white-space:nowrap;">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</th>'
        . '<td style="padding:8px 0;font:400 15px Arial,sans-serif;color:#23282d;">'
        . nl2br(htmlspecialchars($value, ENT_QUOTES, 'UTF-8')) . '</td>'
        . '</tr>';
}

/**
 * Builds the message. Transport is applied separately so the same enquiry can be
 * retried down a different route without rebuilding it.
 */
$compose = static function () use ($config, $email, $name, $htmlRows, $textLines) {
    $mail = new PHPMailer(true);
    $mail->CharSet = PHPMailer::CHARSET_UTF8;
    $mail->Timeout = 20;

    if (!empty($config['debug'])) {
        $mail->SMTPDebug = SMTP::DEBUG_SERVER;
        $mail->Debugoutput = 'error_log';
    }

    // From is always the site's own mailbox, which is what SPF/DKIM are published
    // for. The enquirer goes in Reply-To so replies reach them directly.
    $mail->setFrom($config['from_email'], $config['from_name']);
    $mail->addAddress($config['to_email'], $config['to_name']);
    $mail->addReplyTo($email, $name !== '' ? $name : $email);
    $mail->Subject = 'Website enquiry from ' . ($name !== '' ? $name : 'a visitor');

    $mail->isHTML(true);
    $mail->Body = '<div style="font:400 15px Arial,sans-serif;color:#23282d;">'
        . '<h2 style="font-size:18px;margin:0 0 4px;">New website enquiry</h2>'
        . '<p style="margin:0 0 18px;color:#5f6b76;font-size:14px;">'
        . 'Reply to this email to respond directly to the enquirer.</p>'
        . '<table cellpadding="0" cellspacing="0" border="0">' . $htmlRows . '</table>'
        . '</div>';
    $mail->AltBody = "New website enquiry\n\n" . implode("\n", $textLines);

    return $mail;
};

$sendOverSmtp = static function (PHPMailer $mail) use ($config) {
    $mail->isSMTP();
    $mail->Host = $config['smtp_host'];
    $mail->Port = (int) $config['smtp_port'];
    $mail->SMTPAuth = true;
    $mail->Username = $config['smtp_username'];
    $mail->Password = $config['smtp_password'];
    $mail->SMTPSecure = ($config['smtp_secure'] ?? 'ssl') === 'tls'
        ? PHPMailer::ENCRYPTION_STARTTLS
        : PHPMailer::ENCRYPTION_SMTPS;
    $mail->send();
};

$smtpError = null;

// Primary route: authenticated SMTP as the no-reply mailbox.
if ($transport !== 'sendmail') {
    try {
        $sendOverSmtp($compose());
        $_SESSION['last_enquiry_at'] = $now;
        respond(true, 'Thank you — we will be in touch shortly.');
    } catch (Exception $e) {
        $smtpError = $e->getMessage();
        error_log('mail.php: SMTP send failed — ' . $smtpError);

        if (empty($config['fallback_to_sendmail'])) {
            respond(false, 'Sorry, we could not send your enquiry. Please call us on 07905 184 968.', 500);
        }
    }
}

// Fallback (and the chosen route when transport is 'sendmail'): hand the message
// to the server's own queue so a credential problem never loses an enquiry.
try {
    $mail = $compose();
    $mail->isSendmail();
    $mail->send();

    if ($smtpError !== null) {
        error_log('mail.php: delivered via sendmail after SMTP auth failed. Fix the SMTP credentials.');
    }

    $_SESSION['last_enquiry_at'] = $now;
    respond(true, 'Thank you — we will be in touch shortly.');
} catch (Exception $e) {
    error_log('mail.php: sendmail failed too — ' . $e->getMessage());
    respond(false, 'Sorry, we could not send your enquiry. Please call us on 07905 184 968.', 500);
}
