<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Content-Type: application/json; charset=utf-8");

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  http_response_code(405);
  echo json_encode(["ok"=>false,"message"=>"Only POST allowed"], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  exit;
}

require __DIR__ . "/../includes/conexion.php"; // $pdo

function out($arr, $code = 200) {
  http_response_code($code);
  echo json_encode($arr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  exit;
}
function i($v) { return is_numeric($v) ? (int)$v : null; }
function s($v) { return is_string($v) ? trim($v) : ''; }
function n($v) { return is_numeric($v) ? (float)$v : 0.0; }
function b($v): int {
  if (is_bool($v)) return $v ? 1 : 0;
  $x = strtoupper(trim((string)$v));
  return in_array($x, ['1','TRUE','YES','SI','SÍ','Y'], true) ? 1 : 0;
}
function safe_div($a, $b, $default = 0.0) {
  $a = n($a);
  $b = n($b);
  if (abs($b) < 0.0000001) return $default;
  return $a / $b;
}
function to_local_from_factor($amount, $currencyFactor) {
  $amount = n($amount);
  $currencyFactor = n($currencyFactor);
  if ($currencyFactor <= 0) return $amount;
  return $amount / $currencyFactor;
}
function normalize_date($v): ?string {
  $x = trim((string)$v);
  if ($x === '') return null;
  if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $x)) return $x;
  if (preg_match('/^\d{2}-\d{2}-\d{4}$/', $x)) {
    [$d,$m,$y] = explode('-', $x);
    return sprintf('%04d-%02d-%02d', (int)$y, (int)$m, (int)$d);
  }
  if (preg_match('/^\d{4}-\d{2}-\d{2}\s+/', $x)) return substr($x, 0, 10);
  $ts = strtotime($x);
  if ($ts === false) return null;
  return date('Y-m-d', $ts);
}
function add_days(?string $dateYmd, int $days): ?string {
  if (!$dateYmd) return null;
  $ts = strtotime($dateYmd . ' +' . $days . ' days');
  if ($ts === false) return null;
  return date('Y-m-d', $ts);
}
function pick_first(array $row, array $keys, $default = null) {
  foreach ($keys as $k) {
    if (array_key_exists($k, $row) && $row[$k] !== null && $row[$k] !== '') {
      return $row[$k];
    }
  }
  return $default;
}
function table_exists(PDO $pdo, string $table): bool {
  $st = $pdo->prepare("SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t LIMIT 1");
  $st->execute([':t' => $table]);
  return (bool)$st->fetchColumn();
}
function get_table_columns(PDO $pdo, string $table): array {
  static $cache = [];
  if (isset($cache[$table])) return $cache[$table];
  $st = $pdo->prepare("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t");
  $st->execute([':t' => $table]);
  $cache[$table] = $st->fetchAll(PDO::FETCH_COLUMN) ?: [];
  return $cache[$table];
}
function first_existing_col(PDO $pdo, string $table, array $candidates, ?string $fallback = null): ?string {
  $cols = get_table_columns($pdo, $table);
  foreach ($candidates as $c) {
    if (in_array($c, $cols, true)) return $c;
  }
  return $fallback;
}
function build_dynamic_insert(PDO $pdo, string $table, array $data): ?array {
  if (!table_exists($pdo, $table)) return null;
  $colsDb = get_table_columns($pdo, $table);
  if (!$colsDb) return null;

  $fields = [];
  $params = [];
  $binds  = [];
  $idx = 0;

  foreach ($data as $col => $val) {
    if (!in_array($col, $colsDb, true)) continue;
    $idx++;
    $fields[] = $col;
    $ph = ':' . preg_replace('/[^a-zA-Z0-9_]/', '_', $col) . '_' . $idx;
    $params[] = $ph;
    $binds[$ph] = $val;
  }

  if (empty($fields)) return null;
  $sql = "INSERT INTO {$table} (" . implode(', ', $fields) . ") VALUES (" . implode(', ', $params) . ")";
  return [$sql, $binds];
}
function build_dynamic_update(PDO $pdo, string $table, array $data, string $whereSql, array $whereBind): ?array {
  if (!table_exists($pdo, $table)) return null;
  $colsDb = get_table_columns($pdo, $table);
  if (!$colsDb) return null;

  $sets = [];
  $binds = $whereBind;
  $idx = 0;
  foreach ($data as $col => $val) {
    if (!in_array($col, $colsDb, true)) continue;
    $idx++;
    $ph = ':upd_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $col) . '_' . $idx;
    $sets[] = "{$col} = {$ph}";
    $binds[$ph] = $val;
  }
  if (empty($sets)) return null;
  $sql = "UPDATE {$table} SET " . implode(', ', $sets) . " WHERE {$whereSql}";
  return [$sql, $binds];
}
function normalize_sales_line_type($v): string {
  $x = strtoupper(trim((string)$v));
  $allowed = ['INVENTORY','SERVICE','FIXED_ASSET','OTHER'];
  return in_array($x, $allowed, true) ? $x : 'INVENTORY';
}

$raw = file_get_contents("php://input");
$body = json_decode($raw, true);
if (!is_array($body)) $body = [];

$companyId = i($body['company_id'] ?? $body['id_company'] ?? null);
$orderId   = i($body['order_id'] ?? null);
$userId    = i($body['user_id'] ?? null);
$forceNew  = b($body['force_new'] ?? 0);
$defaultSalesLineType = normalize_sales_line_type($body['default_sales_line_type'] ?? 'INVENTORY');
$lineTypeOverrides = is_array($body['line_type_overrides'] ?? null) ? $body['line_type_overrides'] : [];

if (!$companyId || !$orderId || !$userId) {
  out(["ok"=>false, "message"=>"Faltan parámetros: company_id, order_id, user_id"], 400);
}

if (!table_exists($pdo, 'invoice_sales_header') || !table_exists($pdo, 'invoice_sales_lines')) {
  out(["ok"=>false, "message"=>"No existen las tablas invoice_sales_header e invoice_sales_lines."], 500);
}
if (!table_exists($pdo, 'sales_orders') || !table_exists($pdo, 'sales_order_lines')) {
  out(["ok"=>false, "message"=>"No existen las tablas sales_orders y/o sales_order_lines."], 500);
}

$hdrCompanyCol  = first_existing_col($pdo, 'invoice_sales_header', ['company_id', 'id_company'], 'company_id');
$lineCompanyCol = first_existing_col($pdo, 'invoice_sales_lines', ['company_id', 'id_company'], 'company_id');
$orderCompanyCol = first_existing_col($pdo, 'sales_orders', ['company_id', 'id_company'], 'company_id');

try {
  $pdo->beginTransaction();

  $st = $pdo->prepare("SELECT * FROM sales_orders WHERE {$orderCompanyCol} = :c AND id = :id FOR UPDATE");
  $st->execute([':c'=>$companyId, ':id'=>$orderId]);
  $order = $st->fetch(PDO::FETCH_ASSOC);
  if (!$order) {
    $pdo->rollBack();
    out(["ok"=>false, "message"=>"Pedido de venta no encontrado."], 404);
  }

  $orderStatus = strtoupper(s($order['status'] ?? ''));
  if ($orderStatus === 'CANCELADO') {
    $pdo->rollBack();
    out(["ok"=>false, "message"=>"El pedido está CANCELADO. No se puede facturar."], 409);
  }

  $orderFolio = s($order['folio'] ?? '');
  if ($orderFolio === '') $orderFolio = 'SO-' . $orderId;

  if (!$forceNew) {
    $stEx = $pdo->prepare("\n      SELECT id, folio, status\n      FROM invoice_sales_header\n      WHERE {$hdrCompanyCol} = :c\n        AND source_type = 'SALES_ORDER'\n        AND source_id = :sid\n        AND status IN ('BORRADOR', 'ABIERTO', 'CONFIRMADO')\n      ORDER BY id DESC\n      LIMIT 1\n      FOR UPDATE\n    ");
    $stEx->execute([':c'=>$companyId, ':sid'=>$orderId]);
    $existing = $stEx->fetch(PDO::FETCH_ASSOC);
    if ($existing) {
      $pdo->commit();
      out([
        'ok' => true,
        'message' => 'Ya existe una factura abierta/borrador para este pedido.',
        'order_id' => $orderId,
        'order_folio' => $orderFolio,
        'order_status' => $orderStatus,
        'invoice_id' => (int)$existing['id'],
        'invoice_folio' => s($existing['folio'] ?? ''),
        'invoice_no' => s($existing['folio'] ?? ''),
        'invoice_status' => s($existing['status'] ?? ''),
      ]);
    }
  }

  $stL = $pdo->prepare("SELECT * FROM sales_order_lines WHERE order_id = :oid ORDER BY line_no ASC, id ASC FOR UPDATE");
  $stL->execute([':oid' => $orderId]);
  $lines = $stL->fetchAll(PDO::FETCH_ASSOC) ?: [];
  if (!$lines) {
    $pdo->rollBack();
    out(["ok"=>false, "message"=>"El pedido no tiene líneas."], 400);
  }

  $postingDate = normalize_date(pick_first($body, ['posting_date'], pick_first($order, ['fecha_pedido', 'fecha_registro'], date('Y-m-d')))) ?: date('Y-m-d');
  $invoiceDate = normalize_date(pick_first($body, ['invoice_date'], pick_first($order, ['fecha_pedido', 'fecha_registro'], $postingDate))) ?: $postingDate;

  $exchangeRate = n(pick_first($body, ['exchange_rate'], pick_first($order, ['tipo_cambio'], 1)));
  if ($exchangeRate <= 0) $exchangeRate = 1.0;
  $currencyFactor = n(pick_first($body, ['currency_factor'], pick_first($order, ['currency_factor'], 0)));
  if ($currencyFactor <= 0) {
    $currencyFactor = safe_div(1, $exchangeRate, 1.0);
    if ($currencyFactor <= 0) $currencyFactor = 1.0;
  }
  $currencyCode = s(pick_first($body, ['currency_code'], pick_first($order, ['moneda'], 'MXN')));
  if ($currencyCode === '') $currencyCode = 'MXN';

  $creditDays = i(pick_first($body, ['credit_days'], pick_first($order, ['dias_credito'], 0))) ?? 0;
  $dueDate = normalize_date(pick_first($body, ['due_date'], pick_first($order, ['fecha_vencimiento'], null)));
  if (!$dueDate && $creditDays > 0) $dueDate = add_days($invoiceDate, $creditDays);

  $notes = 'Generado desde pedido de venta';
  $obs = s($order['observaciones'] ?? '');
  if ($obs !== '') $notes .= ' | Obs: ' . $obs;

  $draftFolio = 'SINV-SO-' . $orderId . '-' . date('YmdHis');

  $headerData = [
    'company_id' => $companyId,
    'folio' => $draftFolio,
    'number_series_id' => i(pick_first($body, ['number_series_id'], pick_first($order, ['number_series_id'], null))),
    'temporary_folio' => 1,

    'invoice_type' => s($body['invoice_type'] ?? 'STANDARD'),
    'source_type' => 'SALES_ORDER',
    'source_id' => $orderId,
    'source_no' => $orderFolio,

    'invoice_date' => $invoiceDate,
    'posting_date' => $postingDate,
    'due_date' => $dueDate,

    'customer_id' => i(pick_first($order, ['customer_id'], null)),
    'customer_code' => s(pick_first($order, ['cliente_codigo'], '')),
    'customer_name' => s(pick_first($order, ['cliente_nombre'], '')),
    'customer_tax_id' => s(pick_first($order, ['cliente_rfc'], '')),
    'customer_email' => s(pick_first($order, ['cliente_email'], '')),
    'customer_phone' => s(pick_first($order, ['cliente_telefono'], '')),

    'delivery_street' => s(pick_first($order, ['entrega_calle'], '')),
    'delivery_external_no' => s(pick_first($order, ['entrega_num_ext'], '')),
    'delivery_internal_no' => s(pick_first($order, ['entrega_num_int'], '')),
    'delivery_neighborhood' => s(pick_first($order, ['entrega_colonia'], '')),
    'delivery_zip_code' => s(pick_first($order, ['entrega_cp'], '')),
    'delivery_country' => s(pick_first($order, ['entrega_pais'], 'México')),
    'delivery_contact' => s(pick_first($order, ['entrega_contacto'], '')),
    'delivery_phone' => s(pick_first($order, ['entrega_telefono'], '')),
    'delivery_email' => s(pick_first($order, ['entrega_correo'], '')),
    'delivery_reference' => s(pick_first($order, ['entrega_referencia'], '')),

    'currency_code' => $currencyCode,
    'exchange_rate' => $exchangeRate,
    'currency_factor' => $currencyFactor,
    'credit_days' => $creditDays,
    'sat_payment_method' => s(pick_first($body, ['sat_payment_method'], pick_first($order, ['metodo_pago_sat'], ''))),
    'sat_payment_form' => s(pick_first($body, ['sat_payment_form'], pick_first($order, ['forma_pago_sat'], ''))),
    'sat_cfdi_use' => s(pick_first($body, ['sat_cfdi_use'], pick_first($order, ['uso_cfdi_sat'], ''))),
    'payment_reference' => s(pick_first($body, ['payment_reference'], pick_first($order, ['referencia_pago'], ''))),

    'salesperson_id' => i(pick_first($order, ['vendedor_id'], null)),
    'salesperson_name' => s(pick_first($order, ['vendedor_nombre'], '')),
    'location_id' => i(pick_first($order, ['almacen_origen_id'], null)),
    'location_code' => s(pick_first($order, ['almacen_origen_codigo'], '')),

    'notes' => $notes,

    'subtotal' => 0,
    'vat_amount' => 0,
    'excise_tax' => 0,
    'vat_withholding' => 0,
    'income_tax_withholding' => 0,
    'total' => 0,
    'subtotal_acy' => 0,
    'vat_amount_acy' => 0,
    'excise_tax_acy' => 0,
    'vat_withholding_acy' => 0,
    'income_tax_withholding_acy' => 0,
    'total_acy' => 0,

    'status' => 'BORRADOR',
    'created_by' => $userId,
    'updated_by' => $userId,
    'created_at' => date('Y-m-d H:i:s'),
    'updated_at' => date('Y-m-d H:i:s'),
  ];

  $builtHeader = build_dynamic_insert($pdo, 'invoice_sales_header', $headerData);
  if (!$builtHeader) {
    $pdo->rollBack();
    out(['ok'=>false, 'message'=>'No se pudo construir el INSERT de invoice_sales_header.'], 500);
  }
  [$sqlH, $bindH] = $builtHeader;
  $pdo->prepare($sqlH)->execute($bindH);
  $invoiceId = (int)$pdo->lastInsertId();

  $lineCount = 0;
  $subtotal = 0.0;
  $vatAmount = 0.0;
  $exciseTax = 0.0;
  $vatWithholding = 0.0;
  $incomeTaxWithholding = 0.0;
  $total = 0.0;

  foreach ($lines as $idx => $ln) {
    $lineCount++;

    $sourceLineId = i($ln['id'] ?? null);
    $lineNo = i(pick_first($ln, ['line_no'], null)) ?: (($idx + 1) * 10000);
    $qty = n(pick_first($ln, ['quantity'], 0));
    if ($qty <= 0) {
      $pdo->rollBack();
      out(['ok'=>false, 'message'=>'Hay líneas con quantity <= 0.', 'line_id'=>$sourceLineId], 400);
    }

    $overrideType = $sourceLineId && isset($lineTypeOverrides[(string)$sourceLineId])
      ? $lineTypeOverrides[(string)$sourceLineId]
      : ($lineTypeOverrides[$sourceLineId] ?? null);
    $salesLineType = normalize_sales_line_type($overrideType ?? $defaultSalesLineType);

    $unitPrice = n(pick_first($ln, ['unit_price'], 0));
    $lineSubtotal = n(pick_first($ln, ['line_subtotal'], 0));
    if ($lineSubtotal <= 0 && $qty > 0) $lineSubtotal = round($qty * $unitPrice, 6);

    $ivaImporte = n(pick_first($ln, ['iva_importe'], 0));
    $ivaRetImporte = n(pick_first($ln, ['iva_ret_importe'], 0));
    $isrRetImporte = n(pick_first($ln, ['isr_ret_importe'], 0));
    $iepsImporte = n(pick_first($ln, ['ieps_importe'], 0));
    $lineTotal = n(pick_first($ln, ['line_total'], 0));
    if ($lineTotal <= 0) {
      $lineTotal = $lineSubtotal + $ivaImporte + $iepsImporte - $ivaRetImporte - $isrRetImporte;
    }

    $lineData = [
      'company_id' => $companyId,
      'invoice_id' => $invoiceId,
      'line_no' => $lineNo,
      'sales_line_type' => $salesLineType,

      'item_id' => i(pick_first($ln, ['item_id'], null)),
      'variant_code' => s(pick_first($ln, ['variant_code'], '')) ?: null,
      'item_code' => s(pick_first($ln, ['item_code'], '')),
      'item_description' => s(pick_first($ln, ['item_description'], '')),
      'uom_code' => s(pick_first($ln, ['uom_code'], '')),
      'location_id' => i(pick_first($ln, ['almacen_id'], pick_first($order, ['almacen_origen_id'], null))),
      'location_code' => s(pick_first($ln, ['almacen_codigo'], pick_first($order, ['almacen_origen_codigo'], ''))),

      'quantity' => $qty,
      'unit_price' => $unitPrice,
      'line_subtotal' => $lineSubtotal,
      'impuesto_iva_id' => i(pick_first($ln, ['impuesto_iva_id'], null)),
      'impuesto_iva_ret_id' => i(pick_first($ln, ['impuesto_iva_ret_id'], null)),
      'impuesto_isr_ret_id' => i(pick_first($ln, ['impuesto_isr_ret_id'], null)),
      'impuesto_ieps_id' => i(pick_first($ln, ['impuesto_ieps_id'], null)),
      'iva_importe' => $ivaImporte,
      'iva_ret_importe' => $ivaRetImporte,
      'isr_ret_importe' => $isrRetImporte,
      'ieps_importe' => $iepsImporte,
      'line_total' => $lineTotal,

      'line_subtotal_acy' => round(to_local_from_factor($lineSubtotal, $currencyFactor), 6),
      'iva_importe_acy' => round(to_local_from_factor($ivaImporte, $currencyFactor), 6),
      'iva_ret_importe_acy' => round(to_local_from_factor($ivaRetImporte, $currencyFactor), 6),
      'isr_ret_importe_acy' => round(to_local_from_factor($isrRetImporte, $currencyFactor), 6),
      'ieps_importe_acy' => round(to_local_from_factor($iepsImporte, $currencyFactor), 6),
      'line_total_acy' => round(to_local_from_factor($lineTotal, $currencyFactor), 6),

      'source_type' => 'SALES_ORDER',
      'source_id' => $orderId,
      'source_line_id' => $sourceLineId,
      'source_no' => $orderFolio,

      'sales_order_id' => $orderId,
      'sales_order_line_id' => $sourceLineId,
      'sales_order_no' => $orderFolio,

      'shipment_id' => null,
      'shipment_line_id' => null,
      'shipment_no' => null,

      'qty_to_invoice' => $qty,
      'qty_invoiced' => 0,
      'notes' => null,
      'created_by' => $userId,
      'updated_by' => $userId,
      'created_at' => date('Y-m-d H:i:s'),
      'updated_at' => date('Y-m-d H:i:s'),
    ];

    $builtLine = build_dynamic_insert($pdo, 'invoice_sales_lines', $lineData);
    if (!$builtLine) {
      $pdo->rollBack();
      out(['ok'=>false, 'message'=>'No se pudo construir el INSERT de invoice_sales_lines.', 'line_id'=>$sourceLineId], 500);
    }
    [$sqlLine, $bindLine] = $builtLine;
    $pdo->prepare($sqlLine)->execute($bindLine);

    $subtotal += $lineSubtotal;
    $vatAmount += $ivaImporte;
    $exciseTax += $iepsImporte;
    $vatWithholding += $ivaRetImporte;
    $incomeTaxWithholding += $isrRetImporte;
    $total += $lineTotal;
  }

  $headerUpdate = [
    'subtotal' => round($subtotal, 6),
    'vat_amount' => round($vatAmount, 6),
    'excise_tax' => round($exciseTax, 6),
    'vat_withholding' => round($vatWithholding, 6),
    'income_tax_withholding' => round($incomeTaxWithholding, 6),
    'total' => round($total, 6),
    'subtotal_acy' => round(to_local_from_factor($subtotal, $currencyFactor), 6),
    'vat_amount_acy' => round(to_local_from_factor($vatAmount, $currencyFactor), 6),
    'excise_tax_acy' => round(to_local_from_factor($exciseTax, $currencyFactor), 6),
    'vat_withholding_acy' => round(to_local_from_factor($vatWithholding, $currencyFactor), 6),
    'income_tax_withholding_acy' => round(to_local_from_factor($incomeTaxWithholding, $currencyFactor), 6),
    'total_acy' => round(to_local_from_factor($total, $currencyFactor), 6),
    'updated_by' => $userId,
    'updated_at' => date('Y-m-d H:i:s'),
  ];

  $updHdr = build_dynamic_update($pdo, 'invoice_sales_header', $headerUpdate, "id = :id", [':id' => $invoiceId]);
  if ($updHdr) {
    [$sqlUH, $bindUH] = $updHdr;
    $pdo->prepare($sqlUH)->execute($bindUH);
  }

  $pdo->commit();

  out([
    'ok' => true,
    'message' => 'Factura de venta (BORRADOR) creada correctamente desde pedido.',
    'order_id' => $orderId,
    'order_folio' => $orderFolio,
    'order_status' => $orderStatus,
    'invoice_id' => $invoiceId,
    'invoice_folio' => $draftFolio,
    'invoice_no' => $draftFolio,
    'invoice_status' => 'BORRADOR',
    'line_count' => $lineCount,
    'currency_code' => $currencyCode,
    'exchange_rate' => $exchangeRate,
    'currency_factor' => $currencyFactor,
    'subtotal' => round($subtotal, 6),
    'vat_amount' => round($vatAmount, 6),
    'excise_tax' => round($exciseTax, 6),
    'vat_withholding' => round($vatWithholding, 6),
    'income_tax_withholding' => round($incomeTaxWithholding, 6),
    'total' => round($total, 6),
    'next_step' => 'Revisar/editar la factura y después confirmarla o postearla.'
  ]);

} catch (Throwable $e) {
  if ($pdo->inTransaction()) $pdo->rollBack();
  out(['ok'=>false, 'message'=>'Error al crear factura desde pedido: ' . $e->getMessage()], 500);
}
