<?php
// webhook.php - production-ready ServiceDirect (Svix) webhook receiver
// Location: public_html/servicedirect/webhook.php

// ---------- Configuration & env loader ----------
$envPath = __DIR__ . '/.env';
if (file_exists(__DIR__ . '/config.php')) {
    require_once __DIR__ . '/config.php'; // preferred loader that sets getenv/$_ENV
}
// simple fallback loader if config.php not present
if (file_exists($envPath)) {
    $lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '' || strpos($line, '#') === 0) continue;
        if (strpos($line, '=') === false) continue;
        list($k, $v) = explode('=', $line, 2);
        $k = trim($k); $v = trim($v);
        $v = preg_replace('/^["\'](.*)["\']$/', '$1', $v);
        if (getenv($k) === false) putenv("$k=$v");
        $_ENV[$k] = $v;
        $_SERVER[$k] = $v;
    }
}

function env($name, $default = null) {
    $v = getenv($name);
    if ($v === false) {
        if (isset($_ENV[$name])) return $_ENV[$name];
        if (isset($_SERVER[$name])) return $_SERVER[$name];
        return $default;
    }
    return $v;
}

// env values
$SIGNING_SECRET = env('SIGNING_SECRET', '');
$DB_HOST = env('DB_HOST', 'localhost');
$DB_PORT = (int)env('DB_PORT', 3306);
$DB_NAME = env('DB_NAME', '');
$DB_USER = env('DB_USER', '');
$DB_PASS = env('DB_PASS', '');
$LOG_DIR = env('LOG_DIR', __DIR__ . '/logs');
$EVENTS_DIR = env('EVENTS_DIR', __DIR__ . '/events');
$TOLERANCE_SECONDS = (int)env('TOLERANCE_SECONDS', 300);

// ensure log and events directories exist
if (!is_dir($LOG_DIR)) @mkdir($LOG_DIR, 0755, true);
if (!is_dir($EVENTS_DIR)) @mkdir($EVENTS_DIR, 0755, true);
$LOG_FILE = rtrim($LOG_DIR, '/\\') . '/webhook.log';

function log_line($msg) {
    global $LOG_FILE;
    $t = date('Y-m-d H:i:s');
    @file_put_contents($LOG_FILE, "[$t] $msg\n", FILE_APPEND | LOCK_EX);
}

// ---------- Read request ----------
$rawBody = file_get_contents('php://input');

// fetch headers robustly (getallheaders fallback)
if (function_exists('getallheaders')) {
    $headers = getallheaders();
} else {
    $headers = [];
    foreach ($_SERVER as $key => $value) {
        if (substr($key, 0, 5) === 'HTTP_') {
            $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
            $headers[$name] = $value;
        }
    }
}
// normalize keys to lowercase for easier lookup
$h = [];
foreach ($headers as $k => $v) $h[strtolower($k)] = $v;

// required Svix headers
$svix_id = $h['svix-id'] ?? $h['svix_id'] ?? null;
$svix_ts = $h['svix-timestamp'] ?? $h['svix_timestamp'] ?? null;
$svix_sig = $h['svix-signature'] ?? $h['svix_signature'] ?? null;

// If request not from Svix (e.g. browser GET), show friendly message
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !$svix_id || !$svix_ts || !$svix_sig) {
    // Return 200 intentionally for GET or for safety when opening URL manually
    http_response_code(200);
    echo "Missing svix headers";
    exit;
}

// ---------- Timestamp check (replay protection) ----------
if (!ctype_digit((string)$svix_ts) || abs(time() - (int)$svix_ts) > $TOLERANCE_SECONDS) {
    http_response_code(400);
    log_line("Timestamp outside tolerance. now=" . time() . " svix_ts=$svix_ts");
    echo "Timestamp outside tolerance";
    exit;
}

// ---------- Validate signing secret and signature ----------
if (empty($SIGNING_SECRET)) {
    http_response_code(500);
    log_line("SIGNING_SECRET not configured");
    echo "Server misconfiguration";
    exit;
}
$parts = explode('_', $SIGNING_SECRET, 2);
if (count($parts) !== 2) {
    http_response_code(500);
    log_line("SIGNING_SECRET malformed");
    echo "Server misconfiguration";
    exit;
}
$secret_key = base64_decode($parts[1], true);
if ($secret_key === false) {
    http_response_code(500);
    log_line("Failed to base64-decode SIGNING_SECRET");
    echo "Server misconfiguration";
    exit;
}

// compute expected signature (svix spec)
$signed_content = $svix_id . '.' . $svix_ts . '.' . $rawBody;
$expected_sig = base64_encode(hash_hmac('sha256', $signed_content, $secret_key, true));

// header may contain multiple signatures, split by whitespace
$match = false;
foreach (preg_split('/\s+/', trim($svix_sig)) as $entry) {
    if ($entry === '') continue;
    if (strpos($entry, ',') !== false) list($ver, $val) = explode(',', $entry, 2);
    else $val = $entry;
    if (function_exists('hash_equals')) {
        if (hash_equals($val, $expected_sig)) { $match = true; break; }
    } else {
        if ($val === $expected_sig) { $match = true; break; }
    }
}
if (!$match) {
    http_response_code(400);
    log_line("Signature mismatch. Expected:$expected_sig Header:$svix_sig");
    echo "Signature mismatch";
    exit;
}

// ---------- Parse JSON ----------
$json = json_decode($rawBody, true);
if ($json === null) {
    http_response_code(400);
    log_line("Verified but invalid JSON. raw_len=" . strlen($rawBody));
    echo "Invalid JSON";
    exit;
}

// ---------- Save event JSON file (for debugging) ----------
$evt_file = rtrim($EVENTS_DIR, '/\\') . '/' . date('Ymd_His') . '_' . preg_replace('/[^a-z0-9._-]/i','',($json['type'] ?? 'event')) . '_' . uniqid() . '.json';
@file_put_contents($evt_file, json_encode(['headers'=>$h,'body'=>$json], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));

// ---------- DB connect (optional) ----------
$dbh = null;
if (!empty($DB_NAME) && !empty($DB_USER)) {
    try {
        $dsn = "mysql:host={$DB_HOST};port={$DB_PORT};dbname={$DB_NAME};charset=utf8mb4";
        $dbh = new PDO($dsn, $DB_USER, $DB_PASS, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]);
    } catch (Exception $e) {
        log_line("DB connect failed: " . $e->getMessage());
        // proceed without DB but keep processing
        $dbh = null;
    }
}

// ---------- Route events ----------
$event_type = $json['type'] ?? ($json['event_type'] ?? 'unknown');
$now = date('Y-m-d H:i:s');
log_line("Event received: type=$event_type id={$svix_id} file=" . basename($evt_file));

// handle seller.v1.lead.ready
if (stripos($event_type, 'lead.ready') !== false) {
    $data = $json['data'] ?? [];
    $lead_id = $data['lead_id'] ?? null;

    // DB upsert into sd_leads if DB available
    if ($dbh && $lead_id) {
        try {
            $sql = "INSERT INTO sd_leads (lead_id, lead_creation_timestamp, lead_caller_phone_number, caller_id_name, integration_type, service_category, zip_code, payout, affiliate_payout_outcome, raw_payload)
                    VALUES (:lead_id, :lead_creation_timestamp, :lead_caller_phone_number, :caller_id_name, :integration_type, :service_category, :zip_code, :payout, :affiliate_payout_outcome, :raw_payload)
                    ON DUPLICATE KEY UPDATE
                      lead_creation_timestamp = VALUES(lead_creation_timestamp),
                      lead_caller_phone_number = VALUES(lead_caller_phone_number),
                      caller_id_name = VALUES(caller_id_name),
                      integration_type = VALUES(integration_type),
                      service_category = VALUES(service_category),
                      zip_code = VALUES(zip_code),
                      payout = VALUES(payout),
                      affiliate_payout_outcome = VALUES(affiliate_payout_outcome),
                      raw_payload = VALUES(raw_payload),
                      updated_at = CURRENT_TIMESTAMP";
            $stmt = $dbh->prepare($sql);
            $stmt->execute([
                ':lead_id'=>$lead_id,
                ':lead_creation_timestamp'=> isset($data['lead_creation_timestamp']) ? date('Y-m-d H:i:s', strtotime($data['lead_creation_timestamp'])) : null,
                ':lead_caller_phone_number'=>$data['lead_caller_phone_number'] ?? null,
                ':caller_id_name'=>$data['caller_id_name'] ?? null,
                ':integration_type'=>$data['integration_type'] ?? null,
                ':service_category'=>$data['service_category'] ?? null,
                ':zip_code'=>$data['zip_code'] ?? null,
                ':payout'=>$data['payout'] ?? null,
                ':affiliate_payout_outcome'=>$data['affiliate_payout_outcome'] ?? null,
                ':raw_payload'=>json_encode($data)
            ]);
            log_line("DB upsert sd_leads OK lead_id=$lead_id");
        } catch (Exception $e) {
            log_line("DB upsert sd_leads error: " . $e->getMessage());
        }
    }

    // respond success
    http_response_code(200);
    echo "processed lead.ready";
    exit;
}

// handle seller.v1.lead.update
if (stripos($event_type, 'lead.update') !== false) {
    $data = $json['data'] ?? [];
    $lead_id = $data['lead_id'] ?? null;
    $updates = $data['updates'] ?? [];

    if ($dbh && $lead_id && is_array($updates)) {
        try {
            $ins = $dbh->prepare("INSERT INTO sd_lead_updates (lead_id, tracking_id, property, value, ts) VALUES (:lead_id, :tracking_id, :property, :value, :ts)");
            foreach ($updates as $u) {
                $prop = $u['property'] ?? null;
                $val = $u['value'] ?? null;
                $ts = $u['timestamp'] ?? null;
                $ins->execute([
                    ':lead_id'=>$lead_id,
                    ':tracking_id'=>$data['tracking_id'] ?? null,
                    ':property'=>$prop,
                    ':value'=>json_encode($val),
                    ':ts'=>$ts
                ]);
            }
            log_line("DB insert sd_lead_updates OK lead_id=$lead_id count=" . count($updates));
        } catch (Exception $e) {
            log_line("DB insert sd_lead_updates error: " . $e->getMessage());
        }
    }

    // apply updates to sd_leads if appropriate
    if ($dbh && $lead_id && is_array($updates)) {
        $toUpdate = [];
        foreach ($updates as $u) {
            if (!isset($u['property'])) continue;
            if ($u['property'] === 'payout') $toUpdate['payout'] = $u['value'];
            if ($u['property'] === 'affiliate_payout_outcome') $toUpdate['affiliate_payout_outcome'] = $u['value'];
            if ($u['property'] === 'service_needed') $toUpdate['service_needed'] = $u['value'];
        }
        if ($toUpdate) {
            $sets = []; $params = [':lead_id'=>$lead_id];
            foreach ($toUpdate as $k=>$v) {
                $sets[] = "`$k` = :$k";
                $params[":$k"] = is_array($v) ? json_encode($v) : $v;
            }
            try {
                $sql = "UPDATE sd_leads SET " . implode(',', $sets) . " WHERE lead_id = :lead_id";
                $dbh->prepare($sql)->execute($params);
                log_line("DB update sd_leads OK lead_id=$lead_id");
            } catch (Exception $e) {
                log_line("DB update sd_leads error: " . $e->getMessage());
            }
        }
    }

    http_response_code(200);
    echo "processed lead.update";
    exit;
}

// default: unhandled event type
log_line("Unhandled event type: $event_type");
http_response_code(200);
echo "unhandled event type";
exit;
?>
