// lib/services/receipt_service.dart

import 'package:shared_preferences/shared_preferences.dart';
import 'package:cashumit/models/firefly_account.dart';
import 'package:cashumit/models/receipt_item.dart';
import 'package:cashumit/models/receipt_group.dart';
import 'package:cashumit/models/local_receipt.dart';
import 'package:cashumit/models/submission_task.dart';
import 'package:cashumit/models/group_submission_result.dart';
import 'package:cashumit/services/firefly_api_service.dart';
import 'package:cashumit/services/account_mirror_service.dart';

class ReceiptService {
  /// Memuat kredensial dari shared preferences.
  ///
  /// Mengembalikan Map dengan key 'url' dan 'token' jika kredensial ada dan valid,
  /// atau null jika tidak ditemukan atau tidak valid.
  static Future<Map<String, String>?> loadCredentials() async {
    final prefs = await SharedPreferences.getInstance();
    final url = prefs.getString('firefly_url');
    final token = prefs.getString('firefly_token');

    if (url == null || token == null || url.isEmpty || token.isEmpty) {
      return null;
    }

    return {'url': url, 'token': token};
  }

  /// Memuat daftar akun sumber (revenue) dan tujuan (asset) dari API.
  ///
  /// Mengembalikan daftar akun yang berisi akun revenue dan asset.
  /// Melempar exception jika terjadi kesalahan saat memuat akun.
  static Future<List<Map<String, dynamic>>> loadAccounts({
    required String baseUrl,
    required String accessToken,
  }) async {
    // Mengambil akun revenue
    final revenueAccounts = await FireflyApiService.fetchAccounts(
      baseUrl: baseUrl,
      accessToken: accessToken,
      type: 'revenue',
    );

    // Mengambil akun asset
    final assetAccounts = await FireflyApiService.fetchAccounts(
      baseUrl: baseUrl,
      accessToken: accessToken,
      type: 'asset',
    );

    // Menggabungkan akun revenue dan asset untuk dropdown
    final allAccounts = <Map<String, dynamic>>[];
    for (var account in revenueAccounts) {
      allAccounts.add({
        'id': account.id,
        'name': account.name,
        'type': account.type,
      });
    }
    for (var account in assetAccounts) {
      allAccounts.add({
        'id': account.id,
        'name': account.name,
        'type': account.type,
      });
    }

    return allAccounts;
  }

  /// Mencari ID akun berdasarkan nama dan tipe akun.
  static String? findAccountIdByName({
    required String name,
    required String expectedType,
    required List<Map<String, dynamic>> accounts,
  }) {
    if (name.isEmpty) return null;

    try {
      // Cari akun dengan nama yang cocok dan tipe yang diharapkan
      final account = accounts.firstWhere(
        (account) =>
            account['name'].toString().toLowerCase() == name.toLowerCase() &&
            account['type'] == expectedType,
      );

      return account['id'] as String?;
    } catch (e) {
      // Jika tidak ditemukan, coba pencarian yang lebih fleksibel
      for (var account in accounts) {
        if (account['type'] == expectedType &&
            account['name']
                .toString()
                .toLowerCase()
                .contains(name.toLowerCase())) {
          return account['id'] as String?;
        }
      }

      // Jika masih tidak ditemukan, kembalikan null
      return null;
    }
  }

  /// Generates a transaction description based on item names
  static String generateTransactionDescription(List<ReceiptItem> items) {
    if (items.isEmpty) {
      return 'Transaksi Struk Belanja';
    }

    // Take the first 5 item descriptions
    final itemNames = items.take(5).map((item) => item.description).toList();

    // If there are more than 5 items, append ', dll' to the last item
    if (items.length > 5) {
      itemNames[4] += ', dll';
    }

    // Join the item names with ', '
    return itemNames.join(', ');
  }

  /// Format tanggal untuk API Firefly III (ISO 8601 dengan jam)
  static String _formatFireflyDate(DateTime date) {
    String two(int v) => v.toString().padLeft(2, '0');
    return '${date.year}-${two(date.month)}-${two(date.day)}T${two(date.hour)}:${two(date.minute)}:${two(date.second)}';
  }

  /// Mengirim transaksi ke Firefly III.
  ///
  /// Setiap grup dengan item menjadi satu transaksi `deposit` terpisah
  /// (sumber grup -> tujuan bersama). Jika tip aktif (tipSourceAccountId
  /// tidak null dan paymentAmount > total), satu transaksi tip tambahan
  /// dibuat (sumber = tipSource, tujuan = tujuan bersama, amount = kembalian).
  ///
  /// Mengembalikan list ID transaksi (per grup + tip jika ada).
  /// Melempar exception jika ada error validasi.
  static Future<List<String>> submitTransaction({
    required List<ReceiptGroup> groups,
    required DateTime transactionDate,
    required String destinationAccountId,
    required List<Map<String, dynamic>> accounts,
    required String baseUrl,
    required String accessToken,
    double paymentAmount = 0.0,
    String? tipSourceAccountId,
    double totalAmount = 0.0,
  }) async {
    final groupsWithItems = groups.where((g) => g.items.isNotEmpty).toList();
    if (groupsWithItems.isEmpty) {
      throw Exception('Tidak ada item untuk dikirim');
    }

    // Validasi akun tujuan ada di daftar
    final destinationExists =
        accounts.any((a) => a['id'].toString() == destinationAccountId);
    if (!destinationExists) {
      throw Exception(
          'Akun tujuan tidak ditemukan di daftar akun yang dimuat. Klik "Muat Ulang Akun" dan coba lagi.');
    }

    final fireflyDate = _formatFireflyDate(transactionDate);
    final transactionIds = <String>[];

    // Submit satu transaksi per grup
    for (final group in groupsWithItems) {
      if (!group.hasSource) {
        throw Exception('Setiap grup dengan item harus memiliki akun sumber');
      }
      if (group.sourceAccountId == destinationAccountId) {
        throw Exception(
            'Akun sumber grup "${group.sourceAccountName}" tidak boleh sama dengan akun tujuan');
      }

      final groupTotal = group.total;
      final description = generateTransactionDescription(group.items);

      final id = await FireflyApiService.submitDummyTransaction(
        baseUrl: baseUrl,
        accessToken: accessToken,
        sourceId: group.sourceAccountId!,
        destinationId: destinationAccountId,
        type: 'deposit',
        description: description,
        date: fireflyDate,
        amount: groupTotal.toStringAsFixed(2),
      );

      if (id == null) {
        throw Exception(
            'Gagal mengirim grup "${group.sourceAccountName}" ke Firefly III');
      }
      if (id != 'success') {
        transactionIds.add(id);
      }
    }

    // Submit transaksi tip jika aktif
    final hasTip = tipSourceAccountId != null && paymentAmount > totalAmount;
    if (hasTip) {
      final tipAmount = paymentAmount - totalAmount;
      final tipSourceExists =
          accounts.any((a) => a['id'].toString() == tipSourceAccountId);
      if (!tipSourceExists) {
        throw Exception(
            'Akun sumber tip tidak ditemukan di daftar akun yang dimuat.');
      }
      if (tipSourceAccountId == destinationAccountId) {
        throw Exception('Akun sumber tip tidak boleh sama dengan akun tujuan');
      }

      final tipId = await FireflyApiService.submitDummyTransaction(
        baseUrl: baseUrl,
        accessToken: accessToken,
        sourceId: tipSourceAccountId,
        destinationId: destinationAccountId,
        type: 'deposit',
        description: 'Tip dari kembalian',
        date: fireflyDate,
        amount: tipAmount.toStringAsFixed(2),
      );

      if (tipId == null) {
        throw Exception('Gagal mengirim transaksi tip ke Firefly III');
      }
      if (tipId != 'success') {
        transactionIds.add(tipId);
      }
    }

    return transactionIds;
  }

  /// Mengirim satu grup transaksi ke Firefly III tanpa melempar exception.
  /// Mengembalikan [GroupSubmissionResult] dengan status sukses/gagal.
  ///
  /// Dipakai oleh popup daftar transaksi untuk pengiriman per-item dengan
  /// retry.
  static Future<GroupSubmissionResult> submitSingleGroup({
    required ReceiptGroup group,
    required String destinationAccountId,
    required DateTime transactionDate,
    required String baseUrl,
    required String accessToken,
  }) async {
    final result = GroupSubmissionResult(
      groupId: group.id,
      label: group.sourceAccountName ?? 'Grup',
      amount: group.total,
      isTip: false,
      status: SubmissionStatus.pending,
    );

    if (group.items.isEmpty) {
      return result.copyWith(
        status: SubmissionStatus.failed,
        error: 'Tidak ada item',
      );
    }
    if (!group.hasSource) {
      return result.copyWith(
        status: SubmissionStatus.failed,
        error: 'Akun sumber belum dipilih',
      );
    }
    if (group.sourceAccountId == destinationAccountId) {
      return result.copyWith(
        status: SubmissionStatus.failed,
        error: 'Akun sumber sama dengan tujuan',
      );
    }

    try {
      final description = generateTransactionDescription(group.items);
      final id = await FireflyApiService.submitDummyTransaction(
        baseUrl: baseUrl,
        accessToken: accessToken,
        sourceId: group.sourceAccountId!,
        destinationId: destinationAccountId,
        type: 'deposit',
        description: description,
        date: _formatFireflyDate(transactionDate),
        amount: group.total.toStringAsFixed(2),
      );

      if (id == null) {
        return result.copyWith(
          status: SubmissionStatus.failed,
          error: 'Server mengembalikan respons null',
        );
      }
      // 'success' berarti terkirim tapi ID tidak diparse; anggap sukses tanpa ID
      return result.copyWith(
        status: SubmissionStatus.success,
        transactionId: id == 'success' ? null : id,
      );
    } catch (e) {
      return result.copyWith(
        status: SubmissionStatus.failed,
        error: e.toString(),
      );
    }
  }

  /// Mengirim transaksi tip ke Firefly III tanpa melempar exception.
  static Future<GroupSubmissionResult> submitTip({
    required String tipSourceAccountId,
    required String tipSourceAccountName,
    required double tipAmount,
    required String destinationAccountId,
    required DateTime transactionDate,
    required String baseUrl,
    required String accessToken,
  }) async {
    final result = GroupSubmissionResult(
      groupId: 'tip_${DateTime.now().millisecondsSinceEpoch}',
      label: 'Tip ($tipSourceAccountName)',
      amount: tipAmount,
      isTip: true,
      status: SubmissionStatus.pending,
    );

    if (tipSourceAccountId == destinationAccountId) {
      return result.copyWith(
        status: SubmissionStatus.failed,
        error: 'Akun sumber tip sama dengan tujuan',
      );
    }

    try {
      final id = await FireflyApiService.submitDummyTransaction(
        baseUrl: baseUrl,
        accessToken: accessToken,
        sourceId: tipSourceAccountId,
        destinationId: destinationAccountId,
        type: 'deposit',
        description: 'Tip dari kembalian',
        date: _formatFireflyDate(transactionDate),
        amount: tipAmount.toStringAsFixed(2),
      );

      if (id == null) {
        return result.copyWith(
          status: SubmissionStatus.failed,
          error: 'Server mengembalikan respons null',
        );
      }
      return result.copyWith(
        status: SubmissionStatus.success,
        transactionId: id == 'success' ? null : id,
      );
    } catch (e) {
      return result.copyWith(
        status: SubmissionStatus.failed,
        error: e.toString(),
      );
    }
  }

  /// Fungsi untuk menyimpan akun ke mirror
  static Future<void> saveAccountsToMirror(
      List<Map<String, dynamic>> accounts) async {
    final fireflyAccounts = accounts
        .map((map) => FireflyAccount(
              id: map['id'].toString(),
              name: map['name'].toString(),
              type: map['type'].toString(),
            ))
        .toList();
    await AccountMirrorService.mirrorAccounts(fireflyAccounts);
  }

  /// Fungsi untuk mengambil akun dari mirror
  static Future<List<Map<String, dynamic>>> getMirroredAccounts() async {
    final accounts = await AccountMirrorService.getMirroredAccounts();
    return accounts
        .map((account) => {
              'id': account.id,
              'name': account.name,
              'type': account.type,
            })
        .toList();
  }

  /// Fungsi untuk mendapatkan akun dengan tipe tertentu dari mirror
  static Future<List<Map<String, dynamic>>> getMirroredAccountsWithType(
      String type) async {
    final accounts = await AccountMirrorService.getMirroredAccountsByType(type);
    return accounts
        .map((account) => {
              'id': account.id,
              'name': account.name,
              'type': account.type,
            })
        .toList();
  }

  /// Fungsi untuk memperbarui mirror akun dari server
  static Future<void> updateAccountMirror({
    required String baseUrl,
    required String accessToken,
  }) async {
    try {
      final accounts =
          await loadAccounts(baseUrl: baseUrl, accessToken: accessToken);
      final fireflyAccounts = accounts
          .map((map) => FireflyAccount(
                id: map['id'].toString(),
                name: map['name'].toString(),
                type: map['type'].toString(),
              ))
          .toList();
      await AccountMirrorService.mirrorAccounts(fireflyAccounts);
    } catch (e) {
      print('Gagal memperbarui mirror akun: $e');
    }
  }

  // Fungsi-fungsi untuk default account mapping telah dihapus sesuai permintaan

  /// Fungsi untuk memuat akun dengan fallback mekanisme yang lebih lengkap
  static Future<List<Map<String, dynamic>>> loadAccountsWithFallback({
    required String baseUrl,
    required String accessToken,
  }) async {
    try {
      // Coba ambil dari server terlebih dahulu
      final accounts = await loadAccounts(
        baseUrl: baseUrl,
        accessToken: accessToken,
      );

      // Simpan ke mirror jika berhasil
      await updateAccountMirror(baseUrl: baseUrl, accessToken: accessToken);

      return accounts;
    } catch (serverError) {
      print('Gagal memuat akun dari server: $serverError');

      // Jika gagal dari server, coba dari mirror
      try {
        final mirroredAccounts = await getMirroredAccounts();
        if (mirroredAccounts.isNotEmpty) {
          return mirroredAccounts;
        }
      } catch (mirrorError) {
        print('Gagal memuat dari mirror: $mirrorError');
      }

      // Jika semua fallback gagal, kembalikan list kosong
      return [];
    }
  }

  // ============ TASK BUILDER UNTUK SUBMISSION DIALOG ============

  /// Bangun daftar [SubmissionTask] dari struk saat ini (sebelum kirim).
  ///
  /// Satu task per grup (yang punya item) + satu task tip jika ada.
  static List<SubmissionTask> buildTasksFromReceipt({
    required List<ReceiptGroup> groups,
    required String destinationAccountId,
    required String destinationAccountName,
    required DateTime transactionDate,
    String? tipSourceAccountId,
    String? tipSourceAccountName,
    double? tipAmount,
  }) {
    final tasks = <SubmissionTask>[];
    for (final g in groups.where((g) => g.items.isNotEmpty)) {
      tasks.add(SubmissionTask(
        taskId: g.id,
        group: g,
        destinationAccountId: destinationAccountId,
        destinationAccountName: destinationAccountName,
        transactionDate: transactionDate,
      ));
    }
    if (tipSourceAccountId != null && tipAmount != null && tipAmount > 0) {
      tasks.add(SubmissionTask(
        taskId: 'tip',
        tipAmount: tipAmount,
        tipSourceAccountId: tipSourceAccountId,
        tipSourceAccountName: tipSourceAccountName,
        destinationAccountId: destinationAccountId,
        destinationAccountName: destinationAccountName,
        transactionDate: transactionDate,
      ));
    }
    return tasks;
  }

  /// Bangun daftar [SubmissionTask] dari semua local receipt yang belum
  /// tersubmit. Tiap receipt menghasilkan task per-grup + tip jika ada.
  static List<SubmissionTask> buildTasksFromAllUnsubmittedReceipts(
      List<LocalReceipt> receipts) {
    final tasks = <SubmissionTask>[];
    for (final receipt in receipts.where((r) => !r.isSubmitted)) {
      if (receipt.destinationAccountId == null) continue;
      for (final g in receipt.groups.where((g) => g.items.isNotEmpty)) {
        tasks.add(SubmissionTask(
          taskId: '${receipt.id}_${g.id}',
          group: g,
          destinationAccountId: receipt.destinationAccountId!,
          destinationAccountName: receipt.destinationAccountName ?? '',
          transactionDate: receipt.transactionDate,
        ));
      }
      if (receipt.hasTip) {
        tasks.add(SubmissionTask(
          taskId: '${receipt.id}_tip',
          tipAmount: receipt.tipAmount,
          tipSourceAccountId: receipt.tipSourceAccountId!,
          tipSourceAccountName: receipt.tipSourceAccountName,
          destinationAccountId: receipt.destinationAccountId!,
          destinationAccountName: receipt.destinationAccountName ?? '',
          transactionDate: receipt.transactionDate,
        ));
      }
    }
    return tasks;
  }
}
