namespace foodmarket.Shared.Pos.V1;

// ──────────────────────────────────────────────────────────────────────────────
// Контракты синхронизации сервер ↔ POS (Windows-касса food-market.pos).
// Namespace `Pos.V1` — версионирование на уровне неймспейса: v2 рядом, без
// breaking changes для v1. POS-клиент закрепляет за собой конкретную версию
// через URL `/api/pos/v1/...` (плюс заголовок `X-Pos-Api-Version: 1` для дабл-чека).
//
// Все DTO — record'ы с required-полями: компилятор обяжет POS-разработчика
// заполнить новые поля, если v1.X добавит обязательные. Опциональные поля —
// nullable; default-значения избегаем чтобы baseline на проде совпадал с
// баг-репортами «у меня поля null».
// ──────────────────────────────────────────────────────────────────────────────

/// <summary>Полная карточка товара для оффлайн-кассы. Включает только то, что
/// нужно при пробивке: ID для FK, идентификаторы (имя/артикул/штрихкоды),
/// единица измерения, упаковка (штучная/весовая/разливная), НДС и текущая
/// розничная цена. Изображения и история — не передаём (вес sync-payload'а
/// важен на медленных каналах KZ-периферии).</summary>
public record ProductSyncDto
{
    public required Guid Id { get; init; }
    public required string Name { get; init; }
    public string? Article { get; init; }
    /// <summary>Все штрихкоды товара (для поиска по сканеру). Первый — primary.</summary>
    public required IReadOnlyList<string> Barcodes { get; init; }
    /// <summary>Код единицы измерения (шт., кг, л, м…), не FK — раскрывается на UI.</summary>
    public required string UnitCode { get; init; }
    /// <summary>Тип упаковки: 1=штучный, 2=весовой, 3=разливной (см. Domain.Packaging).</summary>
    public required int Packaging { get; init; }
    public required decimal VatPercent { get; init; }
    public required bool VatEnabled { get; init; }
    public required bool IsMarked { get; init; }
    /// <summary>true — товар архивный, на кассе не продаётся, оставлен для отчётов.</summary>
    public required bool IsArchived { get; init; }
    public DateTime UpdatedAt { get; init; }
}

/// <summary>Розничная цена товара. Один товар может иметь несколько цен
/// (разные типы цен — обычная, для VIP, со скидкой); POS обычно использует
/// «системный/главный» PriceType, его помечает <c>IsSystem=true</c>.</summary>
public record PriceSyncDto
{
    public required Guid ProductId { get; init; }
    public required Guid PriceTypeId { get; init; }
    public required string PriceTypeName { get; init; }
    public required bool IsSystem { get; init; }
    public required decimal Amount { get; init; }
    public required string CurrencyCode { get; init; }
    public DateTime UpdatedAt { get; init; }
}

/// <summary>Текущий остаток на момент запроса. Не агрегируется на POS, просто
/// показывается кассиру для подсказки/предупреждения «осталось 2 шт». Никаких
/// reserve/available — это серверный учёт; на кассе только on-hand.</summary>
public record StockSyncDto
{
    public required Guid ProductId { get; init; }
    public required Guid StoreId { get; init; }
    public required decimal Quantity { get; init; }
    public DateTime AsOf { get; init; }
}

/// <summary>Контрагент (только покупатели — поставщики POS не нужны). Используется
/// для лояльности/привязки чека к клиенту: ввёл телефон → нашёлся контакт.</summary>
public record CounterpartySyncDto
{
    public required Guid Id { get; init; }
    public required string Name { get; init; }
    public string? Phone { get; init; }
    public string? Email { get; init; }
    public string? Bin { get; init; }
    public string? Iin { get; init; }
    public DateTime UpdatedAt { get; init; }
}

/// <summary>Ответ <c>GET /api/pos/sync</c>: все изменения после <c>since</c>.
/// Клиент кеширует <see cref="ServerTime"/> и шлёт её обратно в следующем
/// since — серверная reference time, чтобы клок-дрейф кассы не пропустил
/// записи. <see cref="DeletedProductIds"/> — id товаров, которые с last sync
/// были архивированы (на POS нужно их пометить, не удалять историю продаж).</summary>
public record PosSyncResponse
{
    public required DateTime ServerTime { get; init; }
    public required IReadOnlyList<ProductSyncDto> Products { get; init; }
    public required IReadOnlyList<PriceSyncDto> Prices { get; init; }
    public required IReadOnlyList<StockSyncDto> Stocks { get; init; }
    public required IReadOnlyList<CounterpartySyncDto> Counterparties { get; init; }
    public required IReadOnlyList<Guid> DeletedProductIds { get; init; }
}

/// <summary>Одна продажа из оффлайн-кассы. POS пробил чек локально, теперь
/// шлёт на сервер. <c>ClientSaleId</c> — Guid, который POS присвоил при
/// пробитии; служит частью idempotency-ключа (если батч придёт повторно из-за
/// сетевой ошибки, сервер вернёт прежний результат без двойного списания).
/// Сумма к оплате уже посчитана POS'ом — сервер пересчитывает по своим
/// ценам и сверяет; mismatch → строка попадает в `Failed` ответа.</summary>
public record PosSaleDto
{
    public required Guid ClientSaleId { get; init; }
    public required DateTime OccurredAt { get; init; }
    public Guid? CustomerId { get; init; }
    public Guid? CashierUserId { get; init; }
    public required int Payment { get; init; }      // PaymentMethod enum int (0=Cash,1=Card,2=BankTransfer,3=Bonus,99=Mixed)
    public required decimal PaidCash { get; init; }
    public required decimal PaidCard { get; init; }
    public required IReadOnlyList<PosSaleLineDto> Lines { get; init; }
    public string? Notes { get; init; }
}

public record PosSaleLineDto
{
    public required Guid ProductId { get; init; }
    public required decimal Quantity { get; init; }
    public required decimal UnitPrice { get; init; }
    public required decimal Discount { get; init; }
    public decimal VatPercent { get; init; }
}

/// <summary>Батч продаж POS → сервер. <see cref="IdempotencyKey"/> — Guid,
/// POS должен использовать СТАБИЛЬНЫЙ ключ на тот же набор продаж: повтор
/// батча с тем же ключом гарантирует один и тот же результат без дублей
/// в БД. (Внутри батча уникальность отдельных продаж обеспечивается
/// <c>PosSaleDto.ClientSaleId</c> — он тоже идемпотентен.)</summary>
public record PosSaleBatchDto
{
    public required Guid IdempotencyKey { get; init; }
    public required IReadOnlyList<PosSaleDto> Sales { get; init; }
}

/// <summary>Ответ на загрузку батча.
/// <see cref="Accepted"/> — продажи, которые сервер реально провёл (Status=Posted)
/// или нашёл уже проведённый по ClientSaleId (идемпотентность). С каждой —
/// идентификатор серверной <c>RetailSale</c>.
/// <see cref="Failed"/> — продажи, которые отклонены (mismatch цен, отсутствие
/// товара, нехватка остатка для проведения). POS должен показать кассиру и
/// решить (перепробить с новой ценой, списать как акт расхождения).</summary>
public record PosSaleBatchResponse
{
    public required Guid IdempotencyKey { get; init; }
    public required IReadOnlyList<PosSaleAcceptedDto> Accepted { get; init; }
    public required IReadOnlyList<PosSaleFailedDto> Failed { get; init; }
    /// <summary>true — этот ответ построен по кешу прежнего батча с тем же
    /// ключом, реальной обработки не было.</summary>
    public required bool ReplayedFromCache { get; init; }
}

public record PosSaleAcceptedDto
{
    public required Guid ClientSaleId { get; init; }
    public required Guid ServerSaleId { get; init; }
    public required string ServerSaleNumber { get; init; }
}

public record PosSaleFailedDto
{
    public required Guid ClientSaleId { get; init; }
    public required string Error { get; init; }
    public string? Field { get; init; }
}
