using System.Diagnostics;
using foodmarket.Pos.Core.Data;
using foodmarket.Pos.Core.Logic;
using foodmarket.Pos.Core.Models;
using foodmarket.Pos.Core.Sync;
using foodmarket.Shared.Pos.V1;
namespace foodmarket.Pos.Tooling;
/// CLI-smoke кассы: сценарий «открыть смену → продажи → возврат → закрыть»
/// + офлайн-очередь, через логику pos.core (без UI/дисплея). Работает одинаково
/// на Windows и Linux — для проверки на реальной машине. Запуск: --smoke
public static class SmokeRunner
{
public static int Run(string[] args)
{
var lines = new List();
void Log(string s) { Console.WriteLine(s); lines.Add(s); }
// Холодный старт: от запуска процесса до входа в логику (загрузка рантайма + JIT).
double coldStartMs = 0;
try { coldStartMs = (DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds; } catch { }
var sw = Stopwatch.StartNew();
using var store = PosStore.InMemory();
// Каталог: 30k товаров для замера поиска + несколько «реальных».
var ids = SeedCatalog(store, big: args.Contains("--big"));
var seedMs = sw.ElapsedMilliseconds;
// Замер: поиск по штрихкоду и по имени.
var swBc = Stopwatch.StartNew();
var byBc = store.FindByBarcode("4870000000005");
swBc.Stop();
var swName = Stopwatch.StartNew();
var found = store.SearchByName("товар 1", 20);
swName.Stop();
// Сценарий дня.
var cart = new Cart();
var p1 = store.GetProduct(ids[0])!; var p2 = store.GetProduct(ids[1])!;
cart.AddProduct(p1, 2); cart.AddProduct(p2, 1);
var totals = cart.Totals();
var pay = PaymentMath.Compute(totals.Total, totals.Total + 500m, 0m);
// Положить 5 продаж в очередь (офлайн).
for (int i = 0; i < 5; i++)
store.EnqueueSale(new PosSaleDto
{
ClientSaleId = Guid.NewGuid(), OccurredAt = DateTime.UtcNow, Payment = 0, PaidCash = totals.Total, PaidCard = 0,
Lines = new[] { new PosSaleLineDto { ProductId = ids[0], Quantity = 1m, UnitPrice = p1.Price, Discount = 0m } },
});
var pending = store.PendingCount();
var expected = ShiftMath.ExpectedCash(5000m, 3775m, 1500m, 10000m, 3000m);
var diff = ShiftMath.CashDifference(14775m, expected);
var batchKey = SyncService.StableBatchKey(store.PendingSales().Select(s => s.ClientSaleId));
Log("=== POS smoke ===");
Log($"ОС: {System.Runtime.InteropServices.RuntimeInformation.OSDescription}");
Log($"Холодный старт (процесс→логика): {coldStartMs:F0} мс");
Log($"Каталог засеян: {store.ProductCount()} товаров за {seedMs} мс");
Log($"Поиск по штрихкоду: {(byBc is null ? "НЕ найден" : byBc.Name)} за {swBc.Elapsed.TotalMilliseconds:F2} мс");
Log($"Поиск по имени: {found.Count} совпадений за {swName.Elapsed.TotalMilliseconds:F2} мс");
Log($"Чек: {totals.LineCount} поз., итог {totals.Total} ₸; оплата {pay.Paid} ₸ → сдача {pay.Change} ₸ (достаточно: {pay.Sufficient})");
Log($"Очередь офлайн: {pending} чеков (ожид. 5); idempotency-ключ батча: {batchKey}");
Log($"Смена: должно {expected} ₸, факт 14775 ₸, разница {diff} ₸");
Log($"Всего инициализация+сценарий: {sw.ElapsedMilliseconds} мс");
var ok = byBc is not null && pay.Sufficient && pending == 5 && expected == 14275m && diff == 500m;
Log(ok ? "РЕЗУЛЬТАТ: OK" : "РЕЗУЛЬТАТ: ОШИБКА");
// Результат в файл — на Windows exe собран как WinExe (нет консоли).
var outArg = args.SkipWhile(a => a != "--out").Skip(1).FirstOrDefault();
var outPath = outArg ?? Path.Combine(Path.GetTempPath(), "pos-smoke-result.txt");
try { File.WriteAllText(outPath, string.Join(Environment.NewLine, lines)); } catch { }
return ok ? 0 : 1;
}
private static List SeedCatalog(PosStore store, bool big)
{
var n = big ? 30_000 : 50;
var products = new List(n);
var prices = new List(n);
var ids = new List();
for (int i = 0; i < n; i++)
{
var id = Guid.NewGuid(); ids.Add(id);
var bc = (4_870_000_000_000 + i).ToString();
products.Add(new ProductSyncDto
{
Id = id, Name = $"Товар {i + 1}", Barcodes = new[] { bc }, UnitCode = i % 7 == 0 ? "кг" : "шт",
Packaging = i % 7 == 0 ? 2 : 1, VatPercent = 12m, VatEnabled = true, IsMarked = false, IsArchived = false, UpdatedAt = DateTime.UtcNow,
});
prices.Add(new PriceSyncDto { ProductId = id, PriceTypeId = Guid.Empty, PriceTypeName = "Розн", IsSystem = true, Amount = 100m + i % 900, CurrencyCode = "KZT", UpdatedAt = DateTime.UtcNow });
}
store.ApplySync(new PosSyncResponse
{
ServerTime = DateTime.UtcNow, Products = products, Prices = prices,
Stocks = Array.Empty(), Counterparties = Array.Empty(), DeletedProductIds = Array.Empty(),
});
return ids;
}
}