import 'package:json_annotation/json_annotation.dart';

import '../../domain/entities/inventory_counterparty.dart';
import '../../domain/entities/inventory_event.dart';
import '../../domain/entities/inventory_product.dart';
import '../../domain/entities/inventory_transaction_reference.dart';
import '../../domain/entities/inventory_vendor.dart';
import '../../domain/entities/inventory_warehouse.dart';
import '../../domain/entities/inventory_warehouse_balance.dart';

part 'inventory_common_dtos.g.dart';

@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
class InventoryVendorDto {
  const InventoryVendorDto({this.id, this.vendorName});

  final int? id;
  final String? vendorName;

  factory InventoryVendorDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryVendorDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryVendorDtoToJson(this);

  InventoryVendor toEntity() {
    return InventoryVendor(id: id, name: (vendorName ?? '').trim());
  }
}

@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
class InventoryProductDto {
  const InventoryProductDto({
    required this.id,
    this.productCode,
    this.productName,
    this.vendor,
  });

  final int id;
  final String? productCode;
  final String? productName;
  final InventoryVendorDto? vendor;

  factory InventoryProductDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryProductDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryProductDtoToJson(this);

  InventoryProduct toEntity() {
    return InventoryProduct(
      id: id,
      code: (productCode ?? '').trim(),
      name: (productName ?? '').trim(),
      vendor: vendor?.toEntity(),
    );
  }
}

@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
class InventoryWarehouseDto {
  const InventoryWarehouseDto({
    required this.id,
    this.warehouseCode,
    this.warehouseName,
  });

  final int id;
  final String? warehouseCode;
  final String? warehouseName;

  factory InventoryWarehouseDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryWarehouseDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryWarehouseDtoToJson(this);

  InventoryWarehouse toEntity() => InventoryWarehouse(
    id: id,
    code: (warehouseCode ?? '').trim(),
    name: (warehouseName ?? '').trim(),
  );
}

@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
class InventoryWarehouseBalanceDto {
  const InventoryWarehouseBalanceDto({
    required this.warehouse,
    required this.quantity,
  });

  final InventoryWarehouseDto warehouse;
  @JsonKey(fromJson: _parseQuantity)
  final int quantity;

  factory InventoryWarehouseBalanceDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryWarehouseBalanceDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryWarehouseBalanceDtoToJson(this);

  InventoryWarehouseBalance toEntity() => InventoryWarehouseBalance(
    warehouse: warehouse.toEntity(),
    quantity: quantity,
  );
}

@JsonSerializable(fieldRename: FieldRename.snake)
class InventoryCounterpartyDto {
  const InventoryCounterpartyDto({this.type, this.name});

  final String? type;
  final String? name;

  factory InventoryCounterpartyDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryCounterpartyDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryCounterpartyDtoToJson(this);

  InventoryCounterparty toEntity() {
    final normalized = (type ?? '').toLowerCase();
    InventoryCounterpartyType resolvedType;
    switch (normalized) {
      case 'vendor':
        resolvedType = InventoryCounterpartyType.vendor;
        break;
      case 'customer':
        resolvedType = InventoryCounterpartyType.customer;
        break;
      default:
        resolvedType = InventoryCounterpartyType.unknown;
        break;
    }
    return InventoryCounterparty(type: resolvedType, name: name?.trim());
  }
}

@JsonSerializable(fieldRename: FieldRename.snake)
class InventoryTransactionRefDto {
  const InventoryTransactionRefDto({required this.id, this.transactionNo});

  final int id;
  final String? transactionNo;

  factory InventoryTransactionRefDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryTransactionRefDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryTransactionRefDtoToJson(this);

  InventoryTransactionReference toEntity() => InventoryTransactionReference(
    id: id,
    transactionNo: (transactionNo ?? '').trim(),
  );
}

@JsonSerializable(fieldRename: FieldRename.snake)
class InventoryEventLineRefDto {
  const InventoryEventLineRefDto({
    required this.id,
    this.lineNo,
    this.quantity,
  });

  final int id;
  final int? lineNo;
  @JsonKey(fromJson: _parseNullableQuantity)
  final int? quantity;

  factory InventoryEventLineRefDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryEventLineRefDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryEventLineRefDtoToJson(this);

  InventoryEventLineReference toEntity() => InventoryEventLineReference(
    id: id,
    lineNo: lineNo ?? 0,
    quantity: quantity ?? 0,
  );
}

@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
class InventoryEventDto {
  const InventoryEventDto({
    required this.eventId,
    required this.eventKind,
    required this.eventLabel,
    required this.deltaQuantity,
    required this.occurredAt,
    this.counterparty,
    this.warehouse,
    this.transaction,
    this.line,
  });

  final int eventId;
  final String eventKind;
  final String eventLabel;
  @JsonKey(fromJson: _parseQuantity)
  final int deltaQuantity;
  final DateTime occurredAt;
  final InventoryCounterpartyDto? counterparty;
  final InventoryWarehouseDto? warehouse;
  final InventoryTransactionRefDto? transaction;
  final InventoryEventLineRefDto? line;

  factory InventoryEventDto.fromJson(Map<String, dynamic> json) =>
      _$InventoryEventDtoFromJson(json);

  Map<String, dynamic> toJson() => _$InventoryEventDtoToJson(this);

  InventoryEvent toEntity() => InventoryEvent(
    eventId: eventId,
    eventKind: eventKind,
    eventLabel: eventLabel,
    deltaQuantity: deltaQuantity,
    occurredAt: occurredAt,
    counterparty: counterparty?.toEntity(),
    warehouse: warehouse?.toEntity(),
    transaction: transaction?.toEntity(),
    line: line?.toEntity(),
  );
}

int _parseQuantity(dynamic value) {
  if (value == null) {
    return 0;
  }
  if (value is int) {
    return value;
  }
  if (value is num) {
    return value.round();
  }
  if (value is String) {
    final sanitized = value.replaceAll(',', '').trim();
    if (sanitized.isEmpty) {
      return 0;
    }
    final parsed = num.tryParse(sanitized);
    if (parsed != null) {
      return parsed.round();
    }
  }
  return 0;
}

int? _parseNullableQuantity(dynamic value) {
  if (value == null) {
    return null;
  }
  return _parseQuantity(value);
}
