<?php

// Simple PHP endpoint to create a HubSpot Deal (and optional Contact)
// using data sent from the Shopify product configurator.
//
// Standalone HubSpot configuration (fill in these values).
// NOTE: This script will NOT load `hubspot-config.php`.
// - HUBSPOT_PRIVATE_APP_TOKEN  (required)
// - HUBSPOT_PIPELINE_ID        (required – e.g. default pipeline id)
// - HUBSPOT_DEALSTAGE_ID       (required – stage internal ID in that pipeline)
// - HUBSPOT_PORTAL_ID          (optional – used to build a deal URL)
//
// Required Private App scopes for line items:
// - crm.objects.line_items.read
// - crm.objects.line_items.write
// - e-commerce   (HubSpot “E-commerce” scope — required for line item API)
//
// This endpoint expects a JSON POST body, for example:
// {
//   "product_id": "1234567890",
//   "product_handle": "riverdell-arch-top-door",
//   "product_title": "Riverdell Arch Top Door",
//   "options": { "stile_and_rail_profile": "quarter_bead", ... },
//   "options_summary": "Stile and Rail Profile: Quarter Bead; Height: 80",
//   "line_item_properties": { "Select Wood": "Ash", "Door Unit Design": "single_door" },
//   "quantity": 1,
//   "config_id": "37",
//
// Creates/updates a native HubSpot line item on the deal (name, quantity, price,
// description, hs_sku) instead of deal-level text fields.
//   "price": 1234.56,
//   "currency": "USD",
//   "customer_email": "user@example.com",
//   "customer_first_name": "Jane",
//   "customer_last_name": "Doe"
// }

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['success' => false, 'error' => 'Method not allowed']);
    exit;
}

// ---- Standalone config values ----
// These are intentionally defined inline so this file works on its own.
$HUBSPOT_PRIVATE_APP_TOKEN = 'pat-na1-e082addd-b7fa-4906-b017-d20e881a5883';
$HUBSPOT_PIPELINE_ID = 'default';
$HUBSPOT_DEALSTAGE_ID = 'appointmentscheduled';
$HUBSPOT_PORTAL_ID = '51059215';

// ---- End config values ----

$HS_DEBUG = false;
$HS_LOG_BUFFER = [];

function hs_log($message, $context = null)
{
    global $HS_LOG_BUFFER;
    $entry = [
        'time' => date('c'),
        'message' => (string) $message,
    ];
    if ($context !== null) {
        $entry['context'] = $context;
    }
    $HS_LOG_BUFFER[] = $entry;

    $logDir = __DIR__ . '/logs';
    if (!is_dir($logDir)) {
        @mkdir($logDir, 0755, true);
    }
    $line = $entry['time'] . ' ' . $entry['message'];
    if ($context !== null) {
        $line .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
    $line .= "\n";
    @file_put_contents($logDir . '/hubspot-deal.log', $line, FILE_APPEND | LOCK_EX);
}

function respond($code, $payload)
{
    global $HS_DEBUG, $HS_LOG_BUFFER;
    if ($HS_DEBUG && !isset($payload['log'])) {
        $payload['log'] = $HS_LOG_BUFFER;
    }
    http_response_code($code);
    echo json_encode($payload);
    exit;
}

function get_json_body()
{
    $raw = file_get_contents('php://input');
    if ($raw === false || $raw === '') {
        return null;
    }
    $data = json_decode($raw, true);
    if (!is_array($data)) {
        return null;
    }
    return $data;
}

function hubspot_request($method, $path, $body = null, $logLabel = '')
{
    global $HUBSPOT_PRIVATE_APP_TOKEN;
    $token = (string) $HUBSPOT_PRIVATE_APP_TOKEN;
    if ($token === '') {
        hs_log('HubSpot token missing');
        respond(500, ['success' => false, 'error' => 'HubSpot token not configured']);
    }

    $url = 'https://api.hubapi.com' . $path;
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    $headers = [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json'
    ];
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }

    $responseBody = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($responseBody === false) {
        $err = curl_error($ch);
        curl_close($ch);
        hs_log('cURL error', ['label' => $logLabel, 'method' => $method, 'path' => $path, 'error' => $err]);
        respond(502, ['success' => false, 'error' => 'HubSpot request failed', 'detail' => $err]);
    }
    curl_close($ch);

    $decoded = json_decode($responseBody, true);
    $label = $logLabel !== '' ? $logLabel : ($method . ' ' . $path);
    if ($httpCode < 200 || $httpCode >= 300) {
        hs_log('HubSpot API error', [
            'label' => $label,
            'httpCode' => $httpCode,
            'response' => $decoded,
        ]);
    } else {
        hs_log('HubSpot API ok', ['label' => $label, 'httpCode' => $httpCode]);
    }

    return [$httpCode, $decoded];
}

function find_or_create_contact($email, $firstName = '', $lastName = '')
{
    if (!$email) {
        return null;
    }

    // 1. Try to find existing contact by email
    $searchBody = [
        'filterGroups' => [
            [
                'filters' => [
                    [
                        'propertyName' => 'email',
                        'operator' => 'EQ',
                        'value' => $email
                    ]
                ]
            ]
        ],
        'properties' => ['email']
    ];

    list($code, $data) = hubspot_request('POST', '/crm/v3/objects/contacts/search', $searchBody);
    if ($code >= 200 && $code < 300 && isset($data['results'][0]['id'])) {
        return $data['results'][0]['id'];
    }

    // 2. Create new contact
    $props = ['email' => $email];
    if ($firstName) {
        $props['firstname'] = $firstName;
    }
    if ($lastName) {
        $props['lastname'] = $lastName;
    }

    $createBody = ['properties' => $props];
    list($code2, $data2) = hubspot_request('POST', '/crm/v3/objects/contacts', $createBody);
    if ($code2 >= 200 && $code2 < 300 && isset($data2['id'])) {
        return $data2['id'];
    }

    // If contact creation failed, continue without a contact
    return null;
}

/** HubSpot association type: line item → deal */
define('HS_LINE_ITEM_TO_DEAL_ASSOC_TYPE_ID', 20);

function hs_line_properties_from_body(array $body, $configId = '')
{
    $map = [];
    if (!empty($body['line_item_properties']) && is_array($body['line_item_properties'])) {
        foreach ($body['line_item_properties'] as $k => $v) {
            if (!is_string($k) || $k === '' || $v === null || $v === '') {
                continue;
            }
            $map[$k] = is_scalar($v) ? (string) $v : json_encode($v);
        }
    }
    if (empty($map) && !empty($body['options_summary'])) {
        foreach (explode(';', (string) $body['options_summary']) as $part) {
            $part = trim($part);
            if ($part === '') {
                continue;
            }
            if (preg_match('/^([^:]+):(.+)$/', $part, $m)) {
                $map[trim($m[1])] = trim($m[2]);
            }
        }
    }
    if ($configId !== '' && !isset($map['Config ID'])) {
        $map['Config ID'] = (string) $configId;
    }
    return $map;
}

function hs_parse_quantity(array $body)
{
    if (isset($body['quantity']) && $body['quantity'] !== '' && $body['quantity'] !== null) {
        return max(1, (int) $body['quantity']);
    }
    if (!empty($body['options']) && is_array($body['options']) && isset($body['options']['_quantity'])) {
        return max(1, (int) $body['options']['_quantity']);
    }
    return 1;
}

function hs_unit_and_line_total(array $body)
{
    $qty = hs_parse_quantity($body);

    // Configured / applied line total (estimated price incl. options, × qty when sent from storefront).
    if (isset($body['applied_price']) && $body['applied_price'] !== '' && is_numeric($body['applied_price'])) {
        $lineTotal = round(max(0, (float) $body['applied_price']), 2);
        $unit = $qty > 0 ? round($lineTotal / $qty, 2) : $lineTotal;
        return [$unit, $lineTotal, $qty];
    }

    // Admin path: price is order line total (override or computed × qty).
    if (isset($body['price']) && $body['price'] !== '' && is_numeric($body['price']) && (float) $body['price'] > 0) {
        $lineTotal = round((float) $body['price'], 2);
        $unit = $qty > 0 ? round($lineTotal / $qty, 2) : $lineTotal;
        return [$unit, $lineTotal, $qty];
    }

    $basePrice = isset($body['base_price']) ? (float) $body['base_price'] : 0.0;
    return [$basePrice, round($basePrice * $qty, 2), $qty];
}

function hs_resolve_product_sku(array $body)
{
    if (!empty($body['product_sku'])) {
        return trim((string) $body['product_sku']);
    }
    if (!empty($body['options']) && is_array($body['options']) && !empty($body['options']['_product_sku'])) {
        return trim((string) $body['options']['_product_sku']);
    }
    return '';
}

function hs_infer_product_type_from_title($title)
{
    $t = strtolower(trim((string) $title));
    if ($t === '') {
        return '';
    }
    if (strpos($t, 'screen') !== false && strpos($t, 'storm') !== false) {
        return 'Screen and storm';
    }
    if (strpos($t, 'exterior') !== false) {
        return 'Exterior';
    }
    if (strpos($t, 'interior') !== false) {
        return 'Interior';
    }
    return '';
}

function hs_resolve_product_type(array $body)
{
    if (!empty($body['product_type'])) {
        return trim((string) $body['product_type']);
    }
    if (!empty($body['options']) && is_array($body['options']) && !empty($body['options']['_product_type'])) {
        return trim((string) $body['options']['_product_type']);
    }
    $fromTitle = hs_infer_product_type_from_title($body['product_title'] ?? '');
    if ($fromTitle !== '') {
        return $fromTitle;
    }
    return '';
}

function hs_custom_line_item_property_keys()
{
    return ['door_type', 'product_category', 'screen_type', 'top_type'];
}

/** Drop empty values so PATCH does not clear existing HubSpot fields. */
function hs_prepare_line_item_props_for_api(array $props)
{
    $out = [];
    foreach ($props as $key => $value) {
        if ($value === null) {
            continue;
        }
        if (is_string($value) && trim($value) === '') {
            continue;
        }
        $out[(string) $key] = is_scalar($value) ? (string) $value : json_encode($value);
    }
    return $out;
}

/** Normalize Shopify keys/values to snake_case for lookup tables. */
function hs_normalize_option_key($value)
{
    $v = strtolower(trim((string) $value));
    $v = str_replace('&', ' and ', $v);
    $v = preg_replace('/[^a-z0-9]+/', '_', $v);
    return trim($v, '_');
}

function hs_snake_to_title($key)
{
    $parts = explode('_', (string) $key);
    $out = [];
    foreach ($parts as $part) {
        if ($part === '') {
            continue;
        }
        $out[] = ucfirst($part);
    }
    return implode(' ', $out);
}

function hs_hubspot_product_type_map()
{
    return [
        'screen_and_storm' => 'Screen & Storm',
        'screen_storm' => 'Screen & Storm',
        'interior' => 'Interior',
        'interior_doors' => 'Interior',
        'exterior' => 'Exterior',
        'exterior_doors' => 'Exterior',
    ];
}

function hs_map_product_type_to_hubspot($productType)
{
    $productType = trim((string) $productType);
    if ($productType === '') {
        return '';
    }

    $norm = hs_normalize_option_key($productType);
    $map = hs_hubspot_product_type_map();
    if (isset($map[$norm])) {
        return $map[$norm];
    }

    if (strpos($norm, 'screen') !== false && strpos($norm, 'storm') !== false) {
        return 'Screen & Storm';
    }
    if (strpos($norm, 'interior') !== false) {
        return 'Interior';
    }
    if (strpos($norm, 'exterior') !== false) {
        return 'Exterior';
    }

    return '';
}

function hs_hubspot_wood_map()
{
    return [
        'african_mahogany' => 'African Mahogany',
        'ash' => 'Ash',
        'ash_storm_porch' => 'Ash',
        'black_walnut' => 'Black Walnut',
        'cherry' => 'Cherry',
        'eastern_white_pine' => 'Eastern White Pine',
        'honduran_mahogany' => 'Honduran Mahogany',
        'knotty_alder' => 'Knotty Alder',
        'knotty_pine' => 'Knotty Pine',
        'maple' => 'Maple',
        'poplar' => 'Poplar',
        'quarter_sawn_white_oak' => 'Quarter Sawn White Oak',
        'red_oak' => 'Red Oak',
        'southern_yellow_pine' => 'Southern Yellow Pine',
        'spanish_cedar' => 'Spanish Cedar',
        'vertical_grain_douglas_fir' => 'Vertical Grain Douglas Fir',
        'western_red_cedar' => 'Western Red Cedar',
        'wester_red_cedar' => 'Western Red Cedar',
        'white_oak' => 'White Oak',
    ];
}

function hs_map_wood_to_hubspot($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return '';
    }

    $norm = hs_normalize_option_key($value);
    $map = hs_hubspot_wood_map();
    if (isset($map[$norm])) {
        return $map[$norm];
    }

    // Already a display label (e.g. from line_item_properties).
    if (strpos($value, '_') === false) {
        return $value;
    }

    return hs_snake_to_title($norm);
}

function hs_hubspot_top_style_map()
{
    return [
        'round_top' => 'Round Top',
        'arch_top' => 'Arch Top',
    ];
}

function hs_map_top_style_to_hubspot($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return '';
    }

    $norm = hs_normalize_option_key($value);
    $map = hs_hubspot_top_style_map();
    if (isset($map[$norm])) {
        return $map[$norm];
    }

    if (strpos($value, '_') === false) {
        return $value;
    }

    return hs_snake_to_title($norm);
}

function hs_resolve_raw_option_value(array $options, $optionId)
{
    if (!isset($options[$optionId]) || $options[$optionId] === '' || $options[$optionId] === null) {
        return '';
    }
    $v = $options[$optionId];
    if (is_scalar($v)) {
        return trim((string) $v);
    }
    return '';
}

/** Case-insensitive lookup in line_item_properties map. */
function hs_pick_line_prop(array $map, array $keys)
{
    if (empty($map)) {
        return '';
    }

    $normMap = [];
    foreach ($map as $k => $v) {
        if (!is_string($k) || $k === '' || $v === null || $v === '') {
            continue;
        }
        $normMap[strtolower(trim($k))] = is_scalar($v) ? (string) $v : json_encode($v);
    }

    foreach ($keys as $key) {
        $nk = strtolower(trim((string) $key));
        if ($nk !== '' && isset($normMap[$nk])) {
            return $normMap[$nk];
        }
    }

    return '';
}

function hs_resolve_option_label_from_summary(array $options, $optionId, array $labelHints = [])
{
    $summary = $options['_selected_summary'] ?? null;
    if (!is_array($summary)) {
        return '';
    }

    $wantId = strtolower(trim((string) $optionId));
    foreach ($summary as $item) {
        if (!is_array($item)) {
            continue;
        }
        $id = isset($item['id']) ? strtolower(trim((string) $item['id'])) : '';
        $label = trim((string) ($item['label'] ?? ''));
        $value = trim((string) ($item['value'] ?? ''));
        if ($value === '') {
            continue;
        }
        if ($id !== '' && $id === $wantId) {
            return $value;
        }
        foreach ($labelHints as $hint) {
            $hint = trim((string) $hint);
            if ($hint !== '' && $label !== '' && stripos($label, $hint) !== false) {
                return $value;
            }
        }
    }

    return '';
}

/**
 * Map Shopify product / configurator fields to native HubSpot line item properties.
 * hs_sku, door_type, product_category, screen_type (Select Wood), top_type (top_style).
 */
function hs_map_shopify_to_hubspot_line_props(array $body, array $lineProps)
{
    $mapped = [];

    $sku = hs_resolve_product_sku($body);
    if ($sku !== '') {
        $mapped['hs_sku'] = $sku;
    }

    $productType = hs_resolve_product_type($body);
    $hubspotCategory = hs_map_product_type_to_hubspot($productType);
    if ($hubspotCategory !== '') {
        $mapped['door_type'] = $hubspotCategory;
        $mapped['product_category'] = $hubspotCategory;
    }

    $options = !empty($body['options']) && is_array($body['options']) ? $body['options'] : [];

    // Prefer raw config keys (e.g. african_mahogany) then line-item labels.
    $selectWoodRaw = hs_resolve_raw_option_value($options, 'wood_type');
    if ($selectWoodRaw === '') {
        $selectWoodRaw = hs_resolve_raw_option_value($options, 'wood_type_storm_porch');
    }
    if ($selectWoodRaw === '') {
        $selectWoodRaw = hs_pick_line_prop($lineProps, [
            'Select Wood',
            'Select Wood Type Storm and Porch',
        ]);
    }
    if ($selectWoodRaw === '' && !empty($options)) {
        $selectWoodRaw = hs_resolve_option_label_from_summary($options, 'wood_type', ['Select Wood']);
    }
    if ($selectWoodRaw === '' && !empty($options)) {
        $selectWoodRaw = hs_resolve_option_label_from_summary($options, 'wood_type_storm_porch', ['Select Wood']);
    }
    $screenType = hs_map_wood_to_hubspot($selectWoodRaw);
    if ($screenType !== '') {
        $mapped['screen_type'] = $screenType;
    }

    $topStyleRaw = hs_resolve_raw_option_value($options, 'top_style');
    if ($topStyleRaw === '') {
        $topStyleRaw = hs_pick_line_prop($lineProps, ['Top Style', 'top_style']);
    }
    if ($topStyleRaw === '' && !empty($options)) {
        $topStyleRaw = hs_resolve_option_label_from_summary($options, 'top_style', ['Top Style', 'top style']);
    }
    $topType = hs_map_top_style_to_hubspot($topStyleRaw);
    if ($topType !== '') {
        $mapped['top_type'] = $topType;
    }

    return $mapped;
}

function hs_log_line_item_mapping_diagnostics(array $body, array $lineProps, array $mappedProps, array $finalProps)
{
    $options = !empty($body['options']) && is_array($body['options']) ? $body['options'] : [];
    $productTypeRaw = hs_resolve_product_type($body);
    $issues = [];

    if ($productTypeRaw === '') {
        $issues[] = 'missing_product_type';
    } elseif (empty($mappedProps['door_type'])) {
        $issues[] = 'product_type_not_mapped:' . $productTypeRaw;
    }
    if (empty($mappedProps['screen_type'])) {
        $woodRaw = hs_resolve_raw_option_value($options, 'wood_type');
        if ($woodRaw === '') {
            $woodRaw = hs_resolve_raw_option_value($options, 'wood_type_storm_porch');
        }
        if ($woodRaw === '') {
            $woodRaw = hs_pick_line_prop($lineProps, ['Select Wood', 'Select Wood Type Storm and Porch']);
        }
        $issues[] = $woodRaw === '' ? 'missing_select_wood' : ('screen_type_not_mapped:' . $woodRaw);
    }
    if (empty($mappedProps['top_type'])) {
        $topRaw = hs_resolve_raw_option_value($options, 'top_style');
        if ($topRaw === '') {
            $topRaw = hs_pick_line_prop($lineProps, ['Top Style', 'top_style']);
        }
        $issues[] = $topRaw === '' ? 'missing_top_style' : ('top_type_not_mapped:' . $topRaw);
    }
    if (empty($finalProps['description'])) {
        $issues[] = 'empty_description';
    }

    hs_log('Line item mapping diagnostics', [
        'product_type_raw' => $productTypeRaw !== '' ? $productTypeRaw : null,
        'door_type_mapped' => $mappedProps['door_type'] ?? null,
        'product_category_mapped' => $mappedProps['product_category'] ?? null,
        'screen_type_mapped' => $mappedProps['screen_type'] ?? null,
        'top_type_mapped' => $mappedProps['top_type'] ?? null,
        'wood_type_option' => hs_resolve_raw_option_value($options, 'wood_type') ?: null,
        'wood_type_storm_option' => hs_resolve_raw_option_value($options, 'wood_type_storm_porch') ?: null,
        'top_style_option' => hs_resolve_raw_option_value($options, 'top_style') ?: null,
        'line_item_property_keys' => array_keys($lineProps),
        'options_summary_len' => strlen(trim((string) ($body['options_summary'] ?? ''))),
        'description_len' => strlen(trim((string) ($finalProps['description'] ?? ''))),
        'issues' => $issues,
    ]);
}

function hs_line_item_api_prop_variants(array $props)
{
    $props = hs_prepare_line_item_props_for_api($props);
    $variants = [];
    $variants[] = $props;

    $withoutCurrency = $props;
    unset($withoutCurrency['hs_line_item_currency_code']);
    if ($withoutCurrency !== $props) {
        $variants[] = $withoutCurrency;
    }

    $withoutCustom = $props;
    foreach (hs_custom_line_item_property_keys() as $customKey) {
        unset($withoutCustom[$customKey]);
    }
    if ($withoutCustom !== $props) {
        $variants[] = $withoutCustom;
    }

    $minimal = [
        'name' => $props['name'] ?? 'Door Configuration',
        'quantity' => $props['quantity'] ?? '1',
        'price' => $props['price'] ?? '0.00',
    ];
    if (!empty($props['description'])) {
        $minimal['description'] = $props['description'];
    }
    if (!empty($props['hs_sku'])) {
        $minimal['hs_sku'] = $props['hs_sku'];
    }
    $variants[] = $minimal;

    $unique = [];
    $seen = [];
    foreach ($variants as $variant) {
        $hash = json_encode($variant);
        if (isset($seen[$hash])) {
            continue;
        }
        $seen[$hash] = true;
        $unique[] = $variant;
    }

    return $unique;
}

function hs_read_line_item_properties($lineItemId)
{
    list($code, $data) = hubspot_request(
        'GET',
        '/crm/v3/objects/line_items/' . rawurlencode($lineItemId) . '?properties=' . rawurlencode(
            implode(',', array_merge(
                ['name', 'quantity', 'price', 'amount', 'hs_sku', 'description'],
                hs_custom_line_item_property_keys()
            ))
        ),
        null,
        'read line item ' . $lineItemId
    );

    if ($code >= 200 && $code < 300 && isset($data['properties']) && is_array($data['properties'])) {
        return $data['properties'];
    }

    return [];
}

function hs_build_deal_name($savedProjectName, $productTitle, $email)
{
    $name = trim((string) $savedProjectName);
    if ($name === '') {
        $name = trim((string) $productTitle);
    }
    if ($name === '') {
        $name = 'Door Configuration';
    }
    $email = trim((string) $email);
    if ($email !== '') {
        $name .= ' – ' . $email;
    }
    return $name;
}

function hs_deal_line_total(array $body)
{
    list(, $lineTotal,) = hs_unit_and_line_total($body);
    return $lineTotal;
}

function hs_line_item_scope_error_message($error)
{
    if (!is_array($error)) {
        return '';
    }
    if (($error['category'] ?? '') !== 'MISSING_SCOPES') {
        return '';
    }
    return 'HubSpot Private App is missing line item scopes. In HubSpot go to Settings → Integrations → Private Apps → your app → Scopes, enable “Line items” (read + write) and “E-commerce”, then update the access token in hubspot-create-deal.php.';
}

function hs_config_sku($configId)
{
    $configId = trim((string) $configId);
    return $configId !== '' ? 'CFG-' . $configId : '';
}

function hs_format_line_item_description(array $lineProps, $optionsSummary = '')
{
    $values = [];
    foreach ($lineProps as $label => $value) {
        if ($label === '' || $value === null || (string) $value === '') {
            continue;
        }
        $values[] = (string) $value;
    }
    if (!empty($values)) {
        return implode(' / ', $values);
    }
    return trim((string) $optionsSummary);
}

function hs_build_line_item_properties(array $body, $configId, $productTitle)
{
    list($unitPrice, $lineTotal, $qty) = hs_unit_and_line_total($body);
    $lineProps = hs_line_properties_from_body($body, $configId);

    $description = hs_format_line_item_description($lineProps, $body['options_summary'] ?? '');
    if ($description === '' && $configId !== '') {
        $description = 'Config ID: ' . $configId;
    }
    if (strlen($description) > 65000) {
        $description = substr($description, 0, 65000);
    }

    // HubSpot calculates amount from price × quantity; do not set amount directly.
    $props = [
        'name' => $productTitle !== '' ? $productTitle : 'Door Configuration',
        'quantity' => (string) $qty,
        'price' => number_format($unitPrice, 2, '.', ''),
    ];
    if ($description !== '') {
        $props['description'] = $description;
    }

    $mappedProps = hs_map_shopify_to_hubspot_line_props($body, $lineProps);
    foreach ($mappedProps as $hsKey => $hsValue) {
        if ($hsValue !== '') {
            $props[$hsKey] = $hsValue;
        }
    }

    $currency = isset($body['currency']) ? strtoupper(trim((string) $body['currency'])) : '';
    if ($currency !== '') {
        $props['hs_line_item_currency_code'] = $currency;
    }

    hs_log_line_item_mapping_diagnostics($body, $lineProps, $mappedProps, $props);

    return $props;
}

function hs_get_deal_line_item_ids($dealId)
{
    $path = '/crm/v4/objects/deals/' . rawurlencode($dealId) . '/associations/line_items?limit=100';
    list($code, $data) = hubspot_request('GET', $path, null);
    if (!($code >= 200 && $code < 300) || !isset($data['results']) || !is_array($data['results'])) {
        return [];
    }

    $ids = [];
    foreach ($data['results'] as $row) {
        if (isset($row['toObjectId'])) {
            $ids[] = (string) $row['toObjectId'];
        } elseif (isset($row['id'])) {
            $ids[] = (string) $row['id'];
        }
    }
    return $ids;
}

function hs_batch_read_line_items(array $ids)
{
    if (empty($ids)) {
        return [];
    }

    $inputs = [];
    foreach ($ids as $id) {
        $inputs[] = ['id' => (string) $id];
    }

    list($code, $data) = hubspot_request('POST', '/crm/v3/objects/line_items/batch/read', [
        'properties' => ['name', 'quantity', 'price', 'amount', 'hs_sku', 'description'],
        'inputs' => $inputs,
    ]);

    if (!($code >= 200 && $code < 300) || !isset($data['results']) || !is_array($data['results'])) {
        return [];
    }

    return $data['results'];
}

function hs_find_line_item_id_for_config(array $lineItems, $configId)
{
    $configId = trim((string) $configId);
    if ($configId === '') {
        return null;
    }

    foreach ($lineItems as $item) {
        if (!is_array($item) || empty($item['id'])) {
            continue;
        }
        $desc = isset($item['properties']['description']) ? (string) $item['properties']['description'] : '';
        if ($desc !== '' && preg_match('/Config ID:\s*' . preg_quote($configId, '/') . '(?:\s|$)/i', $desc)) {
            return (string) $item['id'];
        }
    }

    // Legacy line items used CFG-{id} as hs_sku before product SKU was introduced.
    $legacySku = hs_config_sku($configId);
    foreach ($lineItems as $item) {
        if (!is_array($item) || empty($item['id'])) {
            continue;
        }
        $itemSku = isset($item['properties']['hs_sku']) ? (string) $item['properties']['hs_sku'] : '';
        if ($itemSku === $legacySku) {
            return (string) $item['id'];
        }
    }

    return null;
}

function hs_create_line_item_for_deal($dealId, array $props)
{
    $attempts = [];
    $variants = hs_line_item_api_prop_variants($props);
    $lastError = null;
    $variantIndex = 0;

    foreach ($variants as $variantProps) {
        $variantIndex++;
        $body = [
            'properties' => $variantProps,
            'associations' => [
                [
                    'to' => ['id' => (int) $dealId],
                    'types' => [
                        [
                            'associationCategory' => 'HUBSPOT_DEFINED',
                            'associationTypeId' => HS_LINE_ITEM_TO_DEAL_ASSOC_TYPE_ID,
                        ],
                    ],
                ],
            ],
        ];

        list($code, $data) = hubspot_request(
            'POST',
            '/crm/v3/objects/line_items',
            $body,
            'create line item for deal ' . $dealId . ' (variant ' . $variantIndex . ')'
        );
        $attempt = [
            'variant' => $variantIndex,
            'httpCode' => $code,
            'props' => array_keys($variantProps),
            'response' => $data,
        ];
        $attempts[] = $attempt;

        if ($code >= 200 && $code < 300 && isset($data['id'])) {
            if ($variantIndex > 1) {
                hs_log('Line item create used fallback variant', [
                    'dealId' => $dealId,
                    'variant' => $variantIndex,
                    'propsSent' => array_keys($variantProps),
                    'firstAttemptError' => $attempts[0]['response'] ?? null,
                ]);
            }
            return [
                'success' => true,
                'id' => (string) $data['id'],
                'attempts' => $attempts,
                'variantUsed' => $variantIndex,
            ];
        }
        $lastError = $data;
        hs_log('Line item create attempt failed', $attempt);
    }

    return ['success' => false, 'error' => $lastError, 'attempts' => $attempts];
}

function hs_update_line_item($lineItemId, array $props)
{
    $attempts = [];
    $variants = hs_line_item_api_prop_variants($props);
    $lastError = null;
    $variantIndex = 0;

    foreach ($variants as $variantProps) {
        $variantIndex++;
        list($code, $data) = hubspot_request(
            'PATCH',
            '/crm/v3/objects/line_items/' . rawurlencode($lineItemId),
            ['properties' => $variantProps],
            'patch line item ' . $lineItemId . ' (variant ' . $variantIndex . ')'
        );
        $attempt = [
            'variant' => $variantIndex,
            'httpCode' => $code,
            'props' => array_keys($variantProps),
            'response' => $data,
        ];
        $attempts[] = $attempt;

        if ($code >= 200 && $code < 300) {
            if ($variantIndex > 1) {
                hs_log('Line item update used fallback variant', [
                    'lineItemId' => $lineItemId,
                    'variant' => $variantIndex,
                    'propsSent' => array_keys($variantProps),
                    'firstAttemptError' => $attempts[0]['response'] ?? null,
                ]);
            }
            return [
                'success' => true,
                'id' => (string) $lineItemId,
                'attempts' => $attempts,
                'variantUsed' => $variantIndex,
            ];
        }
        $lastError = $data;
        hs_log('Line item update attempt failed', $attempt);
    }

    return ['success' => false, 'error' => $lastError, 'attempts' => $attempts];
}

function hs_sum_line_item_amounts(array $lineItems)
{
    $sum = 0.0;
    foreach ($lineItems as $item) {
        if (!is_array($item)) {
            continue;
        }
        $props = isset($item['properties']) && is_array($item['properties']) ? $item['properties'] : [];
        $amt = isset($props['amount']) ? (float) $props['amount'] : 0.0;
        if ($amt <= 0) {
            $price = isset($props['price']) ? (float) $props['price'] : 0.0;
            $qty = isset($props['quantity']) ? (float) $props['quantity'] : 1.0;
            $amt = $price * $qty;
        }
        $sum += $amt;
    }

    return round($sum, 2);
}

function hs_sync_deal_amount_from_line_items($dealId)
{
    $ids = hs_get_deal_line_item_ids($dealId);
    $items = hs_batch_read_line_items($ids);
    $amount = hs_sum_line_item_amounts($items);
    if ($amount <= 0) {
        return $amount;
    }

    hubspot_request('PATCH', '/crm/v3/objects/deals/' . rawurlencode($dealId), [
        'properties' => ['amount' => $amount],
    ]);

    return $amount;
}

function hs_upsert_deal_line_item($dealId, array $body, $configId, $callRequested, $productTitle)
{
    $props = hs_build_line_item_properties($body, $configId, $productTitle);
    hs_log('Line item props built', [
        'dealId' => $dealId,
        'configId' => $configId,
        'quantity' => $props['quantity'] ?? null,
        'price' => $props['price'] ?? null,
        'sku' => $props['hs_sku'] ?? null,
        'door_type' => $props['door_type'] ?? null,
        'product_category' => $props['product_category'] ?? null,
        'screen_type' => $props['screen_type'] ?? null,
        'top_type' => $props['top_type'] ?? null,
    ]);

    $ids = hs_get_deal_line_item_ids($dealId);
    $existing = hs_batch_read_line_items($ids);
    hs_log('Existing deal line items', ['dealId' => $dealId, 'count' => count($existing), 'ids' => $ids]);

    $targetId = null;
    if ($configId !== '' && ($callRequested === '--' || $callRequested === 'YES')) {
        $targetId = hs_find_line_item_id_for_config($existing, $configId);
    } elseif (count($existing) === 1 && !empty($existing[0]['id'])) {
        $targetId = (string) $existing[0]['id'];
    }

    if ($targetId) {
        hs_log('Updating existing line item', ['lineItemId' => $targetId]);
        $result = hs_update_line_item($targetId, $props);
    } else {
        hs_log('Creating new line item', ['dealId' => $dealId]);
        $result = hs_create_line_item_for_deal($dealId, $props);
    }

    if (empty($result['success'])) {
        hs_log('Line item sync failed', $result);
        $result['dealAmount'] = null;
        return $result;
    }

    $lineItemId = isset($result['id']) ? (string) $result['id'] : '';
    if ($lineItemId !== '') {
        $stored = hs_read_line_item_properties($lineItemId);
        hs_log('Line item stored properties', [
            'lineItemId' => $lineItemId,
            'variantUsed' => $result['variantUsed'] ?? null,
            'description' => $stored['description'] ?? null,
            'hs_sku' => $stored['hs_sku'] ?? null,
            'door_type' => $stored['door_type'] ?? null,
            'product_category' => $stored['product_category'] ?? null,
            'screen_type' => $stored['screen_type'] ?? null,
            'top_type' => $stored['top_type'] ?? null,
        ]);
    }

    $dealAmount = hs_sync_deal_amount_from_line_items($dealId);
    $result['dealAmount'] = $dealAmount;
    hs_log('Line item sync ok', [
        'lineItemId' => $result['id'] ?? null,
        'dealAmount' => $dealAmount,
        'variantUsed' => $result['variantUsed'] ?? null,
    ]);

    return $result;
}

// ---- Main handler ----

$body = get_json_body();
if (!$body) {
    respond(400, ['success' => false, 'error' => 'Invalid or missing JSON']);
}

global $HS_DEBUG;
$HS_DEBUG = !empty($_GET['debug']) || !empty($body['debug']);
hs_log('Request received', [
    'product_title' => $body['product_title'] ?? null,
    'product_type' => $body['product_type'] ?? ($body['options']['_product_type'] ?? null),
    'product_sku' => $body['product_sku'] ?? ($body['options']['_product_sku'] ?? null),
    'deal_id' => $body['deal_id'] ?? null,
    'config_id' => $body['config_id'] ?? null,
    'saved_project_name' => $body['saved_project_name'] ?? null,
    'call_requested' => $body['call_requested'] ?? null,
    'quantity' => $body['quantity'] ?? ($body['options']['_quantity'] ?? null),
    'line_item_property_count' => (isset($body['line_item_properties']) && is_array($body['line_item_properties']))
        ? count($body['line_item_properties'])
        : 0,
    'options_summary_len' => strlen(trim((string) ($body['options_summary'] ?? ''))),
]);

$productTitle = isset($body['product_title']) ? trim((string)$body['product_title']) : '';
$basePrice = isset($body['base_price']) ? (float)$body['base_price'] : null;
list($unitPrice, $lineTotal, $doorQty) = hs_unit_and_line_total($body);
$price = ($basePrice !== null) ? (float)$basePrice : (isset($body['price']) ? (float)$body['price'] : 0.0);
$dealLineTotal = $lineTotal;
$currency = isset($body['currency']) ? strtoupper(trim((string)$body['currency'])) : 'USD';
$optionsSummary = isset($body['options_summary']) ? trim((string)$body['options_summary']) : '';
$productHandle = isset($body['product_handle']) ? trim((string)$body['product_handle']) : '';
$productId = isset($body['product_id']) ? trim((string)$body['product_id']) : '';
$options = isset($body['options']) && is_array($body['options']) ? $body['options'] : [];
$callRequested = isset($body['call_requested']) ? trim((string)$body['call_requested']) : '';
$savedProjectName = isset($body['saved_project_name']) ? trim((string)$body['saved_project_name']) : '';
$requestedDealId = isset($body['deal_id']) ? trim((string)$body['deal_id']) : '';
$configId = isset($body['config_id']) ? trim((string)$body['config_id']) : '';


if ($productTitle === '') {
    respond(400, ['success' => false, 'error' => 'product_title is required']);
}

$pipelineId = $HUBSPOT_PIPELINE_ID;
$dealstageId = $HUBSPOT_DEALSTAGE_ID;
if ($pipelineId === '' || $dealstageId === '') {
    respond(500, ['success' => false, 'error' => 'HubSpot pipeline or dealstage not configured']);
}

$email = isset($body['customer_email']) ? trim((string)$body['customer_email']) : '';
$firstName = isset($body['customer_first_name']) ? trim((string)$body['customer_first_name']) : '';
$lastName = isset($body['customer_last_name']) ? trim((string)$body['customer_last_name']) : '';

$contactId = null;
if ($email !== '') {
    $contactId = find_or_create_contact($email, $firstName, $lastName);
}

// Build configuration summary if not provided
if ($optionsSummary === '' && !empty($options)) {
    $parts = [];
    foreach ($options as $k => $v) {
        if (is_array($v)) {
            $valStr = implode(', ', array_map('strval', $v));
        } else {
            $valStr = (string)$v;
        }
        $parts[] = $k . ': ' . $valStr;
    }
    $optionsSummary = implode('; ', $parts);
}

// Deal properties — name is collection/project label + customer email (not product title).
$now = time() * 1000; // HubSpot expects ms timestamp for close date
$dealName = hs_build_deal_name($savedProjectName, $productTitle, $email);

// Upsert behavior for the configurator:
// - If `deal_id` is provided, patch that deal.
// - Otherwise, try to find a deal by (`saved_project_name`, `call_requested`), and patch it.
// - If no deal is found, create a new one.
$resolvedDealId = $requestedDealId;

if (($resolvedDealId === '') && ($savedProjectName !== '') && ($callRequested !== '')) {
    $searchBody = [
        'filterGroups' => [
            [
                'filters' => [
                    [
                        'propertyName' => 'saved_project_name',
                        'operator' => 'EQ',
                        'value' => $savedProjectName,
                    ],
                    [
                        'propertyName' => 'call_requested',
                        'operator' => 'EQ',
                        'value' => $callRequested,
                    ],
                ],
            ],
        ],
        'properties' => ['dealname', 'amount', 'product_configuration_summary'],
        'limit' => 1,
    ];

    list($searchCode, $searchData) = hubspot_request('POST', '/crm/v3/objects/deals/search', $searchBody, 'search deal');
    if ($searchCode >= 200 && $searchCode < 300 && isset($searchData['results'][0]['id'])) {
        $resolvedDealId = (string) $searchData['results'][0]['id'];
        hs_log('Found existing deal by project', ['dealId' => $resolvedDealId, 'saved_project_name' => $savedProjectName]);
    }
}

$dealId = '';
$dealAction = '';

// ---- PATCH existing deal (replace configuration summary; use base price only) ----
if ($resolvedDealId !== '') {
    $dealId = $resolvedDealId;
    $dealAction = 'patch';

    $fetchPath = '/crm/v3/objects/deals/' . rawurlencode($dealId)
        . '?properties=product_configuration_summary,amount';
    list($fetchCode, $fetchData) = hubspot_request('GET', $fetchPath, null, 'fetch deal ' . $dealId);
    if (!($fetchCode >= 200 && $fetchCode < 300) || !isset($fetchData['properties'])) {
        hs_log('Stale or missing deal_id; creating new deal instead', [
            'resolvedDealId' => $resolvedDealId,
            'fetchCode' => $fetchCode,
            'fetchData' => $fetchData,
        ]);
        $resolvedDealId = '';
        $dealId = '';
        $dealAction = '';
    }
}

if ($resolvedDealId !== '' && $dealId !== '') {

    $oldSummary = isset($fetchData['properties']['product_configuration_summary'])
        ? (string) $fetchData['properties']['product_configuration_summary']
        : '';
    $newSummaryPart = trim((string) $optionsSummary);
    $oldSummaryTrim = trim((string) $oldSummary);

    $combinedSummary = $newSummaryPart;
    $newAmount = (float) $dealLineTotal;

    if ($callRequested === '--' || $callRequested === 'YES') {
        // Segment format:
        // Door #N [Config ID: 123] [Price: 999.99]
        // option summary...
        $segments = [];
        if ($oldSummaryTrim !== '') {
            $rawSegments = preg_split('/\n{2,}/', $oldSummaryTrim);
            if (is_array($rawSegments)) {
                foreach ($rawSegments as $seg) {
                    $segTrim = trim((string) $seg);
                    if ($segTrim !== '') {
                        $segments[] = $segTrim;
                    }
                }
            }
        }

        $replacementSegment = $newSummaryPart;
        if ($newSummaryPart !== '') {
            $replacementHeader = 'Door #X';
            if ($configId !== '') {
                $replacementHeader .= ' [Config ID: ' . $configId . ']';
            }
            $replacementHeader .= ' [Applied Price: ' . number_format((float) $unitPrice, 2, '.', '') . ']';
            if ($doorQty > 1) {
                $replacementHeader .= ' [Qty: ' . $doorQty . '] [Line Total: ' . number_format((float) $dealLineTotal, 2, '.', '') . ']';
            }
            $replacementSegment = $replacementHeader . "\n" . $newSummaryPart;
        }

        $updated = false;
        if ($callRequested === '--' && $configId !== '' && !empty($segments)) {
            foreach ($segments as $idx => $segText) {
                if (preg_match('/\[Config ID:\s*' . preg_quote($configId, '/') . '\]/i', $segText)) {
                    $segments[$idx] = $replacementSegment;
                    $updated = true;
                    break;
                }
            }
        }
        if (!$updated && $replacementSegment !== '') {
            $segments[] = $replacementSegment;
        }

        // Renumber Door # only; do not recompute amount from tags.
        foreach ($segments as $idx => $segText) {
            $doorNo = $idx + 1;
            // Normalize any door index token (including placeholders like Door #X)
            // so saved summary always shows Door #1, Door #2, ...
            $normalized = preg_replace('/^Door\s*#[^\s\[]+/i', 'Door #' . $doorNo, $segText);
            if (!is_string($normalized)) {
                $normalized = $segText;
            }
            $segments[$idx] = $normalized;

        }

        $combinedSummary = implode("\n\n", $segments);
        $newAmount = (float) $dealLineTotal;
    } else {
        if ($oldSummaryTrim !== '' && $newSummaryPart !== '') {
            $combinedSummary = $oldSummaryTrim . "\n\n" . $newSummaryPart;
        } elseif ($oldSummaryTrim !== '') {
            $combinedSummary = $oldSummaryTrim;
        } else {
            $combinedSummary = $newSummaryPart;
        }
        $newAmount = (float) $dealLineTotal;
    }

    $patchProps = [
        'dealname' => $dealName,
        'product_configuration_summary' => $combinedSummary,
        'amount' => $newAmount,
    ];
    if ($callRequested !== '') {
        $patchProps['call_requested'] = $callRequested;
    }
    if ($savedProjectName !== '') {
        $patchProps['saved_project_name'] = $savedProjectName;
    }

    $patchBody = ['properties' => $patchProps];
    list($patchCode, $patchData) = hubspot_request(
        'PATCH',
        '/crm/v3/objects/deals/' . rawurlencode($dealId),
        $patchBody,
        'patch deal ' . $dealId
    );

    if (!($patchCode >= 200 && $patchCode < 300)) {
        hs_log('Deal patch failed', ['dealId' => $dealId, 'patchData' => $patchData]);
        respond(502, ['success' => false, 'error' => 'Failed to patch deal in HubSpot', 'detail' => $patchData]);
    }
    hs_log('Deal patched', ['dealId' => $dealId]);

} else {
    // ---- CREATE new deal ----
    $dealAction = 'create';
    $initialSummary = $optionsSummary;
    if ($callRequested === '--' || $callRequested === 'YES') {
        $initialHeader = 'Door #1';
        if ($callRequested === '--' && $configId !== '') {
            $initialHeader .= ' [Config ID: ' . $configId . ']';
        }
        $initialHeader .= ' [Applied Price: ' . number_format((float) $unitPrice, 2, '.', '') . ']';
        if ($doorQty > 1) {
            $initialHeader .= ' [Qty: ' . $doorQty . '] [Line Total: ' . number_format((float) $dealLineTotal, 2, '.', '') . ']';
        }
        $initialSummary = $initialHeader . "\n" . trim((string) $optionsSummary);
    }

    $dealProps = [
        'dealname' => $dealName,
        'amount' => $dealLineTotal,
        'pipeline' => $pipelineId,
        'dealstage' => $dealstageId,
        'product_configuration_summary' => $initialSummary,
        'closedate' => $now
    ];

    if ($callRequested !== '') {
        $dealProps['call_requested'] = $callRequested;
    }

    if ($savedProjectName !== '') {
        $dealProps['saved_project_name'] = $savedProjectName;
    }

    $dealBody = ['properties' => $dealProps];
    hs_log('Creating deal', [
        'dealname' => $dealName,
        'unitPrice' => $unitPrice,
        'quantity' => $doorQty,
        'amount' => $dealLineTotal,
    ]);
    list($dealCode, $dealData) = hubspot_request('POST', '/crm/v3/objects/deals', $dealBody, 'create deal');
    if (!($dealCode >= 200 && $dealCode < 300) || !isset($dealData['id'])) {
        hs_log('Deal create failed', ['dealData' => $dealData]);
        respond(502, ['success' => false, 'error' => 'Failed to create deal in HubSpot', 'detail' => $dealData]);
    }

    $dealId = (string) $dealData['id'];
    hs_log('Deal created', ['dealId' => $dealId]);
}

// Associate deal with contact if we have one
if ($contactId) {
    // Standard association: deal to contact
    $associationPath = "/crm/v3/objects/deals/{$dealId}/associations/contacts/{$contactId}/deal_to_contact";
    hubspot_request('PUT', $associationPath, null);
}

// Create or update a native HubSpot line item on the deal (name, qty, price, description, SKU).
$lineItemResult = hs_upsert_deal_line_item($dealId, $body, $configId, $callRequested, $productTitle);

// Build a link to the deal in HubSpot UI if portal id is configured (optional)
$portalId = (string) $HUBSPOT_PORTAL_ID;
$dealUrl = null;
if ($portalId) {
    $dealUrl = "https://app.hubspot.com/contacts/{$portalId}/record/0-3/{$dealId}";
}

hs_log('Request complete', [
    'dealId' => $dealId,
    'dealAction' => $dealAction,
    'lineItemSuccess' => !empty($lineItemResult['success']),
]);

$response = [
    'success' => true,
    'dealId' => $dealId,
    'dealAction' => $dealAction,
    'dealUrl' => $dealUrl,
];
if (!empty($lineItemResult['id'])) {
    $response['lineItemId'] = $lineItemResult['id'];
}
if (isset($lineItemResult['dealAmount']) && $lineItemResult['dealAmount'] !== null) {
    $response['dealAmount'] = $lineItemResult['dealAmount'];
}
if (empty($lineItemResult['success'])) {
    $scopeMsg = hs_line_item_scope_error_message($lineItemResult['error'] ?? null);
    $response['lineItemWarning'] = $scopeMsg !== ''
        ? $scopeMsg
        : 'Deal saved but line item sync failed';
    $response['lineItemError'] = $lineItemResult['error'] ?? null;
    if (!empty($lineItemResult['attempts'])) {
        $response['lineItemAttempts'] = $lineItemResult['attempts'];
    }
    // Line items unavailable — keep deal amount at unit price × quantity.
    if ($dealLineTotal > 0) {
        hubspot_request('PATCH', '/crm/v3/objects/deals/' . rawurlencode($dealId), [
            'properties' => ['amount' => $dealLineTotal],
        ], 'patch deal amount (line item fallback)');
        $response['dealAmount'] = $dealLineTotal;
        hs_log('Deal amount set without line items', ['dealId' => $dealId, 'amount' => $dealLineTotal]);
    }
}

respond(200, $response);

