import 'package:holzleitner_api/holzleitner_api.dart' as api;

import 'package:hl_lieferservice/domain/entity/address.dart';
import 'package:hl_lieferservice/domain/entity/article.dart';
import 'package:hl_lieferservice/domain/entity/contact_source.dart';
import 'package:hl_lieferservice/domain/entity/customer.dart';
import 'package:hl_lieferservice/domain/entity/delivery.dart';
import 'package:hl_lieferservice/domain/entity/delivery_credit.dart';
import 'package:hl_lieferservice/domain/entity/delivery_item.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/service.dart';
import 'package:hl_lieferservice/domain/entity/payment_method.dart';
import 'package:hl_lieferservice/domain/entity/scan_intent.dart';
import 'package:hl_lieferservice/domain/entity/scan_progress.dart';
import 'package:hl_lieferservice/domain/entity/tour.dart';
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
import 'package:hl_lieferservice/domain/entity/warehouse.dart';

/// Eine Schicht, ein Mapper-File: alle Übersetzungen vom generierten
/// `built_value`-Client zur Domain. Bewusst pro DTO eine Extension, damit
/// Aufrufer sich nicht in benamten Funktionen verlieren.

// ─── Primitive ────────────────────────────────────────────────────────────

extension ApiAddressMapper on api.Address {
  Address toDomain() => Address(
        street: street,
        houseNumber: houseNumber,
        postalCode: postalCode,
        city: city,
        country: country,
      );
}

// ─── Stammdaten ───────────────────────────────────────────────────────────

extension ApiWarehouseMapper on api.Warehouse {
  Warehouse toDomain() => Warehouse(
        id: id,
        name: name,
        code: code,
        isStandard: isStandard,
      );
}

extension ApiArticleMapper on api.Article {
  Article toDomain() => Article(
        id: id,
        articleNumber: articleNumber,
        name: name,
        scannable: scannable,
        defaultWarehouseId: defaultWarehouseId,
      );
}

extension ApiCustomerMapper on api.Customer {
  Customer toDomain() => Customer(
        id: id,
        name: name,
        erpCustomerId: erpCustomerId,
        address: address.toDomain(),
      );
}

extension ApiCustomerContactMapper on api.CustomerContact {
  CustomerContact toDomain() => CustomerContact(
        id: id,
        customerId: customerId,
        name: name,
        phone: phone,
        email: email,
      );
}

// ─── Beleg-Kontaktquellen (ContactSource / ContactChannel) ───────────────
//
// Der OpenAPI-Generator erzeugt für die snake-case-serde-Enums im Backend
// `EnumClass`-Wrapper mit camelCase-Identifiern. Verglichen wird wie bei
// `ScanStatus` per Identitäts-Check; Fallback ist ein StateError, damit
// neue Backend-Werte sofort auffallen statt schweigend zu mappen.

extension ApiContactRoleMapper on api.ContactRole {
  ContactRole toDomain() {
    if (this == api.ContactRole.header) return ContactRole.header;
    if (this == api.ContactRole.delivery) return ContactRole.delivery;
    if (this == api.ContactRole.billing) return ContactRole.billing;
    if (this == api.ContactRole.contactPerson) return ContactRole.contactPerson;
    if (this == api.ContactRole.customerMaster) {
      return ContactRole.customerMaster;
    }
    throw StateError('Unbekannte ContactRole vom Backend: $this');
  }
}

extension ApiContactKindMapper on api.ContactKind {
  ContactKind toDomain() {
    if (this == api.ContactKind.phone) return ContactKind.phone;
    if (this == api.ContactKind.mobile) return ContactKind.mobile;
    if (this == api.ContactKind.email) return ContactKind.email;
    if (this == api.ContactKind.web) return ContactKind.web;
    throw StateError('Unbekannter ContactKind vom Backend: $this');
  }
}

extension ApiContactSourceMapper on api.ContactSource {
  ContactSource toDomain() => ContactSource(
        id: id,
        deliveryId: deliveryId,
        role: role.toDomain(),
        anrede: anrede,
        titel: titel,
        name1: name1,
        name2: name2,
        name3: name3,
        abteilung: abteilung,
        funktion: funktion,
      );
}

extension ApiContactChannelMapper on api.ContactChannel {
  ContactChannel toDomain() => ContactChannel(
        id: id,
        sourceId: sourceId,
        kind: kind.toDomain(),
        position: position,
        value: value,
      );
}

// ─── Scan-Progress & Delivery-Item ───────────────────────────────────────

extension ApiScanStateMapper on api.ScanState {
  ScanProgress toDomain() => ScanProgress(
        status: status.toDomain(),
        scannedQuantity: scannedQuantity,
        creditedQuantity: creditedQuantity,
        lastUpdatedAt: lastUpdatedAt,
        heldReason: heldReason,
      );
}

// ─── Scan-Apply ──────────────────────────────────────────────────────────

extension DomainScanActionMapper on ScanAction {
  api.AuditAction toWire() {
    switch (this) {
      case ScanAction.scan:
        return api.AuditAction.scan;
      case ScanAction.unscan:
        return api.AuditAction.unscan;
      case ScanAction.hold:
        return api.AuditAction.hold;
      case ScanAction.unhold:
        return api.AuditAction.unhold;
      case ScanAction.remove:
        return api.AuditAction.remove;
      case ScanAction.unremove:
        return api.AuditAction.unremove;
    }
  }
}

extension DomainScanIntentMapper on ScanIntent {
  api.ScanEvent toWire() => api.ScanEvent((b) {
        b
          ..clientScanId = clientScanId
          ..clientScannedAt = clientScannedAt.toUtc()
          ..deliveryItemId = deliveryItemId
          ..action = action.toWire()
          ..actorCarId = actorCarId
          ..reason = reason
          // Nur für remove/unremove relevant; null = ganze Restmenge.
          ..quantity = quantity
          ..manual = manual;
      });
}

extension ApiScanResultStatusMapper on api.ScanResultStatus {
  ScanOutcomeStatus toDomain() {
    if (this == api.ScanResultStatus.applied) return ScanOutcomeStatus.applied;
    if (this == api.ScanResultStatus.duplicate) {
      return ScanOutcomeStatus.duplicate;
    }
    if (this == api.ScanResultStatus.rejected) {
      return ScanOutcomeStatus.rejected;
    }
    throw StateError('Unbekannter ScanResultStatus vom Backend: $this');
  }
}

extension ApiScanResultMapper on api.ScanResult {
  ScanOutcome toDomain() => ScanOutcome(
        clientScanId: clientScanId,
        status: status.toDomain(),
        deliveryItemId: deliveryItemId,
        reason: reason,
      );
}

extension ApiScanStatusMapper on api.ScanStatus {
  ScanStatus toDomain() {
    // EnumClass kennt keinen `switch`-Exhaustiveness-Check; deshalb explizit.
    if (this == api.ScanStatus.inProgress) return ScanStatus.inProgress;
    if (this == api.ScanStatus.done) return ScanStatus.done;
    if (this == api.ScanStatus.held) return ScanStatus.held;
    if (this == api.ScanStatus.removed) return ScanStatus.removed;
    throw StateError('Unbekannter ScanStatus vom Backend: $this');
  }
}

extension ApiDeliveryItemMapper on api.DeliveryItem {
  DeliveryItem toDomain() => DeliveryItem(
        id: id,
        deliveryId: deliveryId,
        articleId: articleId,
        warehouseId: warehouseId,
        belegzeilenNr: belegzeilenNr,
        requiredQuantity: requiredQuantity,
        scanProgress: scanState.toDomain(),
        unitPrice: unitPrice,
        komponentenArtikelNr: komponentenArtikelNr,
        parentArtikelNr: parentArtikelNr,
      );
}

// ─── Delivery ────────────────────────────────────────────────────────────

extension ApiDeliveryStateMapper on api.DeliveryState {
  DeliveryState toDomain() {
    if (this == api.DeliveryState.active) return DeliveryState.active;
    if (this == api.DeliveryState.held) return DeliveryState.held;
    if (this == api.DeliveryState.canceled) return DeliveryState.canceled;
    if (this == api.DeliveryState.completed) return DeliveryState.completed;
    throw StateError('Unbekannter DeliveryState vom Backend: $this');
  }
}

extension ApiDeliveryWithItemsMapper on api.DeliveryWithItems {
  Delivery toDomain() => Delivery(
        id: id,
        tourId: tourId,
        customerId: customerId,
        contactPersonIds: contactPersonIds.toList(growable: false),
        deliveryAddressSnapshot: deliveryAddressSnapshot.toDomain(),
        erpBelegartId: erpBelegartId,
        erpBelegnummer: erpBelegnummer,
        state: state.toDomain(),
        stateReason: stateReason,
        sortOrder: sortOrder,
        assignedCarId: assignedCarId,
        desiredTime: desiredTime,
        specialAgreements: specialAgreements,
        items: items.map((it) => it.toDomain()).toList(growable: false),
        prepaidAmount: prepaidAmount,
        paymentMethodId: paymentMethodId,
      );
}

extension ApiPaymentMethodMapper on api.PaymentMethod {
  PaymentMethod toDomain() => PaymentMethod(
        id: id,
        code: code,
        name: name,
        active: active,
        createdAt: createdAt,
      );
}

// ─── Tour-Notiz ──────────────────────────────────────────────────────────

extension ApiDeliveryNoteMapper on api.DeliveryNote {
  DeliveryNote toDomain() => DeliveryNote(
        id: id,
        deliveryId: deliveryId,
        text: text,
        imageAttachment: imageAttachment,
        authorPersonalnummer: authorPersonalnummer,
        authorCarId: authorCarId,
        creditDeliveryItemId: creditDeliveryItemId,
        isAmountCreditNote: isAmountCreditNote,
        imageAttachmentDeleted: imageAttachmentDeleted ?? false,
        createdAt: createdAt,
      );
}

// ─── Tour-Wurzel ─────────────────────────────────────────────────────────

extension ApiTourMapper on api.Tour {
  Tour toDomain() => Tour(
        id: id,
        accountId: accountId,
        date: date.toDateTime(),
        syncedAt: syncedAt,
      );
}

extension ApiTourSummaryMapper on api.TourSummary {
  TourSummary toDomain() => TourSummary(
        tourId: tourId,
        tourDate: tourDate.toDateTime(),
        deliveryCount: deliveryCount,
      );
}

extension ApiTourDetailsMapper on api.TourDetails {
  TourDetails toDomain() {
    final customersMap = <String, Customer>{
      for (final c in customers) c.id: c.toDomain(),
    };
    final contactsMap = <String, CustomerContact>{
      for (final c in customerContacts) c.id: c.toDomain(),
    };
    final articlesMap = <String, Article>{
      for (final a in articles) a.id: a.toDomain(),
    };
    final warehousesMap = <String, Warehouse>{
      for (final w in warehouses) w.id: w.toDomain(),
    };

    // Notizen sind im Wire flach — pro Lieferung indizieren und aufsteigend
    // nach createdAt sortieren, damit das UI sich nicht jedes Mal selbst
    // sortieren muss.
    final notesGrouped = <String, List<DeliveryNote>>{};
    for (final n in notes) {
      final domain = n.toDomain();
      (notesGrouped[domain.deliveryId] ??= <DeliveryNote>[]).add(domain);
    }
    for (final list in notesGrouped.values) {
      list.sort((a, b) => a.createdAt.compareTo(b.createdAt));
    }

    // Gutschriften: höchstens eine pro Lieferung (aktueller Stand).
    final creditsMap = <String, DeliveryCredit>{
      for (final c in credits) c.deliveryId: c.toDomain(),
    };

    // Service-Definitionen (aktiv, sortiert) + Pro-Lieferung-Werte indizieren.
    final servicesList =
        services.map((s) => s.toDomain()).toList(growable: false);
    final serviceValues = <String, Map<String, DeliveryServiceValue>>{};
    for (final v in deliveryServices) {
      (serviceValues[v.deliveryId] ??= <String, DeliveryServiceValue>{})[
          v.serviceId] = v.toDomain();
    }

    // Kontaktquellen pro Lieferung gruppieren; Kanäle pro Quelle gruppieren.
    // Backend liefert sie sortiert (Quellen nach Rolle, Kanäle nach kind +
    // position) — wir behalten die Reihenfolge bei.
    final sourcesGrouped = <String, List<ContactSource>>{};
    for (final s in contactSources) {
      final domain = s.toDomain();
      (sourcesGrouped[domain.deliveryId] ??= <ContactSource>[]).add(domain);
    }
    final channelsGrouped = <String, List<ContactChannel>>{};
    for (final c in contactChannels) {
      final domain = c.toDomain();
      (channelsGrouped[domain.sourceId] ??= <ContactChannel>[]).add(domain);
    }

    return TourDetails(
      tour: tour.toDomain(),
      deliveries: deliveries.map((d) => d.toDomain()).toList(growable: false),
      customers: customersMap,
      contacts: contactsMap,
      articles: articlesMap,
      warehouses: warehousesMap,
      notesByDeliveryId: notesGrouped,
      creditsByDeliveryId: creditsMap,
      services: servicesList,
      serviceValuesByDeliveryId: serviceValues,
      contactSourcesByDeliveryId: sourcesGrouped,
      contactChannelsBySourceId: channelsGrouped,
    );
  }
}

extension ApiServiceMapper on api.Service {
  Service toDomain() => Service(
        id: id,
        key: key,
        name: name,
        kind: kind == api.ServiceKind.numeric
            ? ServiceKind.numeric
            : ServiceKind.boolean,
        active: active,
        sortOrder: sortOrder,
        minValue: minValue,
        maxValue: maxValue,
      );
}

extension ApiDeliveryServiceValueMapper on api.DeliveryServiceValue {
  DeliveryServiceValue toDomain() => DeliveryServiceValue(
        deliveryId: deliveryId,
        serviceId: serviceId,
        boolValue: boolValue,
        numericValue: numericValue,
      );
}

extension ApiDeliveryCreditMapper on api.DeliveryCredit {
  DeliveryCredit toDomain() => DeliveryCredit(
        deliveryId: deliveryId,
        amountCents: amountCents,
        reason: reason,
      );
}
