import 'package:hl_lieferservice/domain/entity/delivery.dart';
import 'package:hl_lieferservice/domain/entity/delivery_credit.dart';
import 'package:hl_lieferservice/domain/entity/delivery_note.dart';
import 'package:hl_lieferservice/domain/entity/delivery_service_value.dart';
import 'package:hl_lieferservice/domain/entity/scan_intent.dart';
import 'package:hl_lieferservice/domain/entity/tour.dart';
import 'package:hl_lieferservice/domain/entity/tour_details.dart';

/// Port für das Tour-Aggregat.
///
/// Der Port deckt in dieser Migrations-Phase nur die Read-Seite + die
/// beiden Operationen, die zum Loading-Flow zwingend gebraucht werden:
/// Sortierung und Fahrzeug-Zuweisung. Hold/Resume/Cancel/Complete und
/// Notizen werden in C+D-4 nachgezogen, damit das hier nicht überladen
/// wird und der Bloc fokussiert bleibt.
///
/// Account-Filter sitzt serverseitig im JWT — der Client schickt nie eine
/// `personalnummer`/`accountId` mit.
abstract interface class TourRepository {
  /// Die heutige Tour-Übersicht des angemeldeten Fahrers oder `null`,
  /// wenn keine Tour für heute angelegt ist (ERP-Sync noch nicht
  /// gelaufen, Treiber-Urlaub etc.).
  ///
  /// Liefert nur die schlanke `TourSummary`-Repräsentation;
  /// [getTourDetails] zieht dann den vollen Aggregat-Snapshot.
  Future<TourSummary?> getMyTourSummaryOfToday();

  /// Lädt das volle Tour-Aggregat (Tour + Lieferungen + Items +
  /// Stammdaten + Notizen) für die gegebene Tour-Id.
  Future<TourDetails> getTourDetails(String tourId);

  /// Convenience: kombiniert [getMyTourSummaryOfToday] + [getTourDetails]
  /// und gibt `null` zurück, wenn keine Tour existiert. Verwendet die App
  /// beim Initial-Load.
  Future<TourDetails?> getMyTourDetailsOfToday();

  /// Schreibt die Sortier-Reihenfolge der Lieferungen einer Tour neu.
  ///
  /// `orderedDeliveryIds` muss **alle** Lieferungen der Tour enthalten,
  /// in der gewünschten Reihenfolge — das Backend lehnt unvollständige
  /// Listen mit `400 validation` ab.
  ///
  /// Rückgabe: deliveryId → neuer sortOrder (für den Bloc-Reducer).
  Future<Map<String, int>> setDeliveryOrder({
    required String tourId,
    required List<String> orderedDeliveryIds,
  });

  /// Weist einer Lieferung ein Fahrzeug zu. `carId == null` löst die
  /// bestehende Zuweisung. Der Server gibt die aktualisierte Delivery
  /// zurück; weil dieser Endpoint nur Stamm-Felder mutiert, ist es Aufgabe
  /// des Aufrufers, die `items` aus dem lokalen Aggregat zu erhalten.
  ///
  /// Rückgabe: die Stamm-Delivery **ohne** Items — Aufrufer nutzt
  /// `copyWith(items: ...)` zum Mergen mit dem lokalen State.
  Future<Delivery> assignCarToDelivery({
    required String deliveryId,
    required String? carId,
  });

  /// Bricht eine Lieferung ab — endgültig (`canceled`). `reason` ist
  /// vom Backend Pflicht; leere Begründungen werden mit 400 abgelehnt.
  /// Rückgabe: Server-Snapshot der Delivery **ohne** Items.
  Future<Delivery> cancelDelivery({
    required String deliveryId,
    required String reason,
  });

  /// Pausiert eine Lieferung (`held`). Reversibel über [resumeDelivery].
  /// `reason` ist Pflicht.
  Future<Delivery> holdDelivery({
    required String deliveryId,
    required String reason,
  });

  /// Setzt eine pausierte Lieferung auf `active` zurück. Kein Reason
  /// erforderlich.
  Future<Delivery> resumeDelivery({required String deliveryId});

  /// Schließt eine Lieferung ab (`completed`). Lädt beide Unterschriften
  /// (Kunde + Fahrer, PNG) per multipart hoch und dokumentiert die
  /// Bestätigungen des Kunden. Atomar serverseitig — das Backend prüft
  /// vorher: Lieferung aktiv, alle scanbaren Positionen fertig, Notizen
  /// bestätigt (falls vorhanden). [paymentMethodId] persistiert die ggf. im
  /// Summary geänderte Zahlungsmethode (muss existieren + aktiv sein); `null`
  /// lässt die am Beleg hinterlegte Methode unangetastet. Rückgabe:
  /// Server-Snapshot der Delivery **ohne** Items (Aufrufer merged Items aus
  /// dem lokalen Aggregat).
  Future<Delivery> completeDelivery({
    required String deliveryId,
    required List<int> customerSignaturePng,
    required List<int> driverSignaturePng,
    required bool receiptConfirmed,
    required bool notesAcknowledged,
    required List<String> acknowledgedNoteIds,
    String? paymentMethodId,
    String? actorCarId,
    bool paymentCollected = false,
  });

  /// Legt eine neue Notiz an einer Lieferung an.
  ///
  /// Mindestens eines von [text] und [imageAttachment] muss inhaltlich
  /// gefüllt sein — das Backend erzwingt das. Aktuell unterstützt die App
  /// nur den Text-Pfad; das `imageAttachment`-Feld bleibt der zukünftigen
  /// Foto-Upload-Phase vorbehalten.
  ///
  /// Rückgabe: die neu angelegte Notiz (mit Server-gesetzter `id` und
  /// `createdAt`) — der Aufrufer hängt sie an das lokale Tour-Aggregat.
  Future<DeliveryNote> addDeliveryNote({
    required String deliveryId,
    String? text,
    String? imageAttachment,
    String? creditDeliveryItemId,
    bool isAmountCreditNote,
  });

  /// Ändert Text/Bild einer bestehenden Notiz. Mindestens eines von [text]
  /// und [imageAttachment] muss inhaltlich gefüllt sein. Rückgabe: die
  /// aktualisierte Notiz (Autor/`createdAt` bleiben).
  Future<DeliveryNote> updateDeliveryNote({
    required String deliveryId,
    required String noteId,
    String? text,
    String? imageAttachment,
  });

  /// Löscht eine Notiz. Innerhalb des (geteilten) Accounts darf jeder Fahrer
  /// löschen — keine Autor-Prüfung serverseitig.
  Future<void> deleteDeliveryNote({
    required String deliveryId,
    required String noteId,
  });

  /// Lädt ein Bild zu einer Lieferung hoch (multipart, Feld `file`). Das
  /// Backend reicht es an DOCUframe weiter und legt eine Bild-Notiz mit der
  /// zurückgelieferten Referenz (`~ObjectID`) als `imageAttachment` an.
  /// Rückgabe: die neue Notiz.
  Future<DeliveryNote> uploadDeliveryNoteImage({
    required String deliveryId,
    required String filename,
    required String mime,
    required List<int> bytes,
  });

  /// Setzt/ändert die Betrags-Gutschrift einer Lieferung. Append-only +
  /// idempotent über [clientEventId]. Rückgabe: aktueller Stand (`null`, wenn
  /// — theoretisch — nichts gesetzt ist).
  Future<DeliveryCredit?> setDeliveryCredit({
    required String deliveryId,
    required String clientEventId,
    required int amountCents,
    required String reason,
    String? actorCarId,
  });

  /// Entfernt die Betrags-Gutschrift einer Lieferung (append-only `remove`).
  /// Rückgabe: aktueller Stand danach (`null`).
  Future<DeliveryCredit?> removeDeliveryCredit({
    required String deliveryId,
    required String clientEventId,
    String? actorCarId,
  });

  /// Setzt (Upsert) den Wert eines Service für eine Lieferung. Genau das zum
  /// Service-Typ passende Feld angeben. Rückgabe: der gespeicherte Wert.
  Future<DeliveryServiceValue> setDeliveryService({
    required String deliveryId,
    required String serviceId,
    bool? boolValue,
    int? numericValue,
    String? actorCarId,
  });

  /// Entfernt den Service-Wert einer Lieferung (Service „nicht gesetzt").
  Future<void> removeDeliveryService({
    required String deliveryId,
    required String serviceId,
  });

  /// Wendet eine Liste Scan-Ereignisse als Batch am Server an.
  ///
  /// Der Endpoint ist bewusst Bulk: damit kann der Client einen
  /// Scanner-Burst (z. B. 5 Barcodes in 2 Sekunden) in einem HTTP-Call
  /// abschicken, **muss** aber nicht — auch ein Aufruf mit nur einem
  /// `ScanIntent` ist erlaubt.
  ///
  /// Idempotenz: das Backend speichert pro `clientScanId` einmal. Wer
  /// retried, bekommt `duplicate` zurück; doppelte Anwendung kann es
  /// nicht geben.
  ///
  /// Rückgabe: pro Eingabe-Intent ein [ScanOutcome] (Key =
  /// `clientScanId`). Die Map enthält **jeden** Intent, auch
  /// `rejected`-Fälle; bei Netzwerk-/Server-Fehlern wirft das Repository
  /// stattdessen [TourRepositoryException], die Map ist dann nicht
  /// teilweise gefüllt.
  Future<Map<String, ScanOutcome>> applyScans(List<ScanIntent> intents);
}

/// Allgemeine Repository-Exception für Tour-Operationen. Konkrete Impls
/// dürfen spezifischere Subtypen werfen.
class TourRepositoryException implements Exception {
  const TourRepositoryException(this.message, [this.cause]);

  final String message;
  final Object? cause;

  @override
  String toString() => 'TourRepositoryException: $message';
}
