import 'package:uuid/uuid.dart';

import '../models/sales_entry_models.dart';
import 'sales_entry_repository.dart';
import 'sales_receipt_repository.dart';

class SalesReceiptService {
  SalesReceiptService({
    SalesReceiptRepository? receiptRepository,
    SalesEntryRepository? entryRepository,
  })  : _receiptRepository = receiptRepository ?? SalesReceiptRepository(),
        _entryRepository = entryRepository ?? SalesEntryRepository();

  final SalesReceiptRepository _receiptRepository;
  final SalesEntryRepository _entryRepository;
  final Uuid _uuid = const Uuid();

  Future<List<SalesReceipt>> fetchReceipts({DateTime? startDate, DateTime? endDate}) {
    return _receiptRepository.fetchReceipts(startDate: startDate, endDate: endDate);
  }

  Future<Map<String, int>> fetchAllocatedTotals(Iterable<String> entryIds) {
    return _receiptRepository.fetchAllocatedTotals(entryIds);
  }

  Future<List<SalesReceiptLink>> fetchLinks(String receiptId) {
    return _receiptRepository.fetchLinks(receiptId);
  }

  Future<SalesReceipt?> findById(String id) {
    return _receiptRepository.findById(id);
  }

  Future<void> deleteReceipt(String id) {
    return _receiptRepository.deleteReceipt(id);
  }

  Future<SalesReceipt> createReceipt({
    String? customerId,
    required DateTime paymentDate,
    required int amount,
    String? method,
    String? notes,
    List<SalesReceiptAllocationInput> allocations = const [],
  }) async {
    if (amount <= 0) {
      throw ArgumentError('amount must be greater than 0');
    }
    final receipt = SalesReceipt(
      id: _uuid.v4(),
      customerId: customerId,
      paymentDate: paymentDate,
      method: method,
      amount: amount,
      notes: notes,
      createdAt: DateTime.now(),
      updatedAt: DateTime.now(),
    );
    return _saveReceipt(receipt: receipt, allocations: allocations);
  }

  Future<SalesReceipt> updateReceipt({
    required SalesReceipt receipt,
    List<SalesReceiptAllocationInput> allocations = const [],
  }) {
    final updated = receipt.copyWith(updatedAt: DateTime.now());
    return _saveReceipt(receipt: updated, allocations: allocations);
  }

  Future<SalesReceipt> _saveReceipt({
    required SalesReceipt receipt,
    required List<SalesReceiptAllocationInput> allocations,
  }) async {
    final entries = await _loadEntries(allocations.map((a) => a.salesEntryId));
    final allocatedTotals = await _receiptRepository.fetchAllocatedTotals(entries.keys);

    final links = <SalesReceiptLink>[];
    for (final allocation in allocations) {
      final entry = entries[allocation.salesEntryId];
      if (entry == null) {
        throw StateError('売上伝票が見つかりません: ${allocation.salesEntryId}');
      }
      final currentAllocated = allocatedTotals[entry.id] ?? 0;
      final outstanding = entry.amountTaxIncl - currentAllocated;
      if (allocation.amount > outstanding) {
        throw StateError('割当額が未収残を超えています: ${entry.id}');
      }
      links.add(
        SalesReceiptLink(
          receiptId: receipt.id,
          salesEntryId: entry.id,
          allocatedAmount: allocation.amount,
        ),
      );
      allocatedTotals[entry.id] = currentAllocated + allocation.amount;
    }

    final totalAllocated = links.fold<int>(0, (sum, link) => sum + link.allocatedAmount);
    if (totalAllocated > receipt.amount) {
      throw StateError('割当総額が入金額を超えています');
    }

    await _receiptRepository.upsertReceipt(receipt, links);
    await _updateEntryStatuses(entries.values, allocatedTotals);
    return receipt;
  }

  Future<void> _updateEntryStatuses(Iterable<SalesEntry> entries, Map<String, int> allocatedTotals) async {
    for (final entry in entries) {
      final allocated = allocatedTotals[entry.id] ?? 0;
      SalesEntryStatus newStatus;
      if (allocated >= entry.amountTaxIncl) {
        newStatus = SalesEntryStatus.settled;
      } else if (allocated > 0) {
        newStatus = SalesEntryStatus.confirmed;
      } else {
        newStatus = entry.status;
      }
      if (newStatus != entry.status) {
        await _entryRepository.upsertEntry(entry.copyWith(status: newStatus, updatedAt: DateTime.now()));
      }
    }
  }

  Future<Map<String, SalesEntry>> _loadEntries(Iterable<String> entryIds) async {
    final map = <String, SalesEntry>{};
    for (final id in entryIds) {
      final entry = await _entryRepository.findById(id);
      if (entry != null) {
        map[id] = entry;
      }
    }
    return map;
  }
}
