import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
import 'package:cashumit/models/receipt_item.dart';
import 'package:cashumit/models/receipt_group.dart';
import 'package:flutter_esc_pos_utils/flutter_esc_pos_utils.dart';
import 'package:image/image.dart' as img;
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:cashumit/services/print_config.dart';

/// Data class untuk konfigurasi toko
class StoreConfig {
  final String name;
  final String address;
  final String adminName;
  final String adminPhone;
  final String? logoPath;
  final String disclaimer;
  final String thankYouText;
  final String pantunText;

  StoreConfig({
    required this.name,
    required this.address,
    required this.adminName,
    required this.adminPhone,
    this.logoPath,
    required this.disclaimer,
    required this.thankYouText,
    required this.pantunText,
  });

  static Future<StoreConfig> fromSharedPreferences() async {
    final prefs = await SharedPreferences.getInstance();

    // Baca semua nilai sekaligus untuk menghindari multiple disk reads
    final storeName = prefs.getString('store_name') ?? 'TOKO SEMBAKO MURAH';
    final storeAddress =
        prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
    final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
    final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
    final logoPath = prefs.getString('store_logo_path');
    final disclaimer = prefs.getString('store_disclaimer_text') ??
        'Barang yang sudah dibeli tidak dapat dikembalikan/ditukar. '
            'Harap periksa kembali struk belanja Anda sebelum meninggalkan toko.';
    final thankYouText =
        prefs.getString('thank_you_text') ?? '*** TERIMA KASIH ***';
    final pantunText = prefs.getString('pantun_text') ??
        'Belanja di toko kami, hemat dan nyaman\n'
            'Dengan penuh semangat, kami siap melayani\n'
            'Harapan kami, Anda selalu puas\n'
            'Sampai jumpa lagi, selamat tinggal.';

    return StoreConfig(
      name: storeName,
      address: storeAddress,
      adminName: adminName,
      adminPhone: adminPhone,
      logoPath: logoPath,
      disclaimer: disclaimer,
      thankYouText: thankYouText,
      pantunText: pantunText,
    );
  }
}

/// Data class untuk informasi pembayaran
class PaymentInfo {
  final double paymentAmount;
  final bool isTip;

  PaymentInfo({
    this.paymentAmount = 0.0,
    this.isTip = false,
  });
}

/// Fungsi top-level untuk decode + resize logo di isolate terpisah.
/// Mengembalikan byte PNG hasil resize (isolate-safe, tidak mengirim objek Image).
Future<Uint8List?> _decodeAndResizeLogo(String path) async {
  final file = File(path);
  if (!await file.exists()) return null;
  final bytes = await file.readAsBytes();
  final decoded = img.decodeImage(bytes);
  if (decoded == null) return null;
  final resized = img.copyResize(decoded, width: 300);
  // Encode ke PNG agar bisa diteruskan keluar isolate
  return Uint8List.fromList(img.encodePng(resized));
}

/// Service untuk menghasilkan perintah ESC/POS menggunakan flutter_esc_pos_utils
class EscPosPrintService {
  /// Menghasilkan struk dalam format byte array berdasarkan data transaksi
  static Future<List<int>> generateEscPosBytes({
    required List<ReceiptGroup> groups,
    required DateTime transactionDate,
    String? destinationAccountName,
    PaymentInfo? paymentInfo,
    String? tipSourceAccountName,
  }) async {
    // Load store info from shared preferences once
    final storeConfig = await StoreConfig.fromSharedPreferences();

    // Format tanggal
    final dateFormatter = DateFormat('dd/MM/yyyy HH:mm');
    final formattedDate = dateFormatter.format(transactionDate);

    // Load capability profile
    final profile = await _loadCapabilityProfile();

    // Get printer configuration
    final config = await PrintConfig.loadPrinterConfig();
    final paperSize = PrintConfig.getPaperSizeEnum(config['paperSize']);

    // Use configured paper size or default
    final generator = Generator(
        paperSize == 'mm80' ? PaperSize.mm80 : PaperSize.mm58, profile);

    // Generate all receipt sections
    List<int> bytes = [];

    bytes.addAll(generator.reset());
    bytes.addAll([0x1D, 0x21, 0x00]); // GS ! 0 = paksa ukuran karakter normal

    // Add store logo if available (diproses di isolate terpisah)
    bytes.addAll(await _addStoreLogo(generator, storeConfig.logoPath));

    // Add store header
    bytes.addAll(_addStoreHeader(generator, storeConfig, formattedDate));

    final groupsWithItems = groups.where((g) => g.items.isNotEmpty).toList();

    // Cetak setiap grup
    for (int gi = 0; gi < groupsWithItems.length; gi++) {
      final group = groupsWithItems[gi];

      // Header grup: satu garis + nama sumber (tanpa "Dari:")
      bytes.addAll(generator.text('--------------------------------',
          styles: PosStyles(align: PosAlign.center)));
      bytes.addAll(generator.text(group.sourceAccountName ?? '(belum dipilih)',
          styles: PosStyles(align: PosAlign.center, bold: true)));

      // Item list grup
      bytes.addAll(_addItemList(generator, group.items));

      // Subtotal grup
      bytes.addAll(generator.text(
        _padRow('Subtotal', _formatRupiah(group.total)),
        styles: PosStyles(align: PosAlign.left, bold: true),
      ));

      if (gi < groupsWithItems.length - 1) {
        bytes.addAll(generator.feed(1));
      }
    }

    // Add grand totals + payment
    bytes.addAll(_addTotals(generator, groupsWithItems, paymentInfo,
        tipSourceAccountName: tipSourceAccountName));

    // Add footer information
    bytes.addAll(_addFooter(generator, storeConfig));

    // Add cut command
    bytes.addAll(generator.feed(2));
    bytes.addAll(generator.cut());

    return bytes;
  }

  /// Load capability profile safely
  static Future<CapabilityProfile> _loadCapabilityProfile() async {
    try {
      return await CapabilityProfile.load();
    } catch (e) {
      // Fallback: profile default pakai nama generic
      try {
        return await CapabilityProfile.load();
      } catch (_) {
        // Jika masih gagal, lempar agar caller tahu
        rethrow;
      }
    }
  }

  /// Add store logo to receipt (image processing di isolate terpisah)
  static Future<List<int>> _addStoreLogo(
      Generator generator, String? logoPath) async {
    List<int> bytes = [];
    if (logoPath != null && logoPath.isNotEmpty) {
      try {
        // Decode + resize di isolate terpisah agar tidak memblok UI thread
        final resizedBytes = await compute(_decodeAndResizeLogo, logoPath);
        if (resizedBytes != null) {
          final logo = img.decodeImage(resizedBytes);
          if (logo != null) {
            bytes.addAll(generator.image(logo, align: PosAlign.center));
            bytes.addAll(generator.feed(1));
          }
        }
      } catch (e) {
        print('Error loading or processing store logo: $e');
      }
    }
    return bytes;
  }

  /// Add store header information
  static List<int> _addStoreHeader(
      Generator generator, StoreConfig config, String formattedDate) {
    List<int> bytes = [];

    // Add store name as header
    bytes.addAll(generator.text(config.name,
        styles: PosStyles(
          bold: true,
          height: PosTextSize.size1,
          width: PosTextSize.size1,
          align: PosAlign.center,
        )));

    bytes.addAll(generator.text(config.address,
        styles: PosStyles(align: PosAlign.center)));
    bytes.addAll(generator.text('Admin: ${config.adminName}',
        styles: PosStyles(align: PosAlign.center)));
    bytes.addAll(generator.text('Telp: ${config.adminPhone}',
        styles: PosStyles(align: PosAlign.center)));
    bytes.addAll(generator.text(formattedDate,
        styles: PosStyles(align: PosAlign.center)));

    bytes.addAll(generator.feed(1));

    return bytes;
  }

  /// Add item list to receipt
  static List<int> _addItemList(Generator generator, List<ReceiptItem> items) {
    List<int> bytes = [];

    for (int i = 0; i < items.length; i++) {
      try {
        var item = items[i];

        bytes.addAll(generator.row([
          PosColumn(
            text: item.description,
            width: 12,
            styles: PosStyles(align: PosAlign.left),
          ),
        ]));
        bytes.addAll(generator.text(
          _padRow(_compactQtyPrice(item.quantity, item.price), _formatRupiahCompact(item.total)),
          styles: PosStyles(align: PosAlign.left),
        ));
      } catch (e) {
        continue;
      }
    }

    return bytes;
  }

  /// Add totals and payment information
  static List<int> _addTotals(
      Generator generator, List<ReceiptGroup> groups, PaymentInfo? paymentInfo,
      {String? tipSourceAccountName}) {
    List<int> bytes = [];

    // Separator before total
    bytes.addAll(generator.text('================================',
        styles: PosStyles(align: PosAlign.center)));

    // Calculate grand total
    double totalAmount = groups.fold(0.0, (sum, group) => sum + group.total);

    // Add total row
    bytes.addAll(generator.text(
      _padRow('TOTAL', _formatRupiah(totalAmount)),
      styles: PosStyles(bold: true, align: PosAlign.left),
    ));

    // Add payment information if provided
    if (paymentInfo != null && paymentInfo.paymentAmount > 0) {
      bytes.addAll(_addPaymentInfo(generator, totalAmount, paymentInfo,
          tipSourceAccountName: tipSourceAccountName));
    }

    return bytes;
  }

  /// Add payment information including change and tip
  static List<int> _addPaymentInfo(
      Generator generator, double totalAmount, PaymentInfo paymentInfo,
      {String? tipSourceAccountName}) {
    List<int> bytes = [];
    final changeAmount = paymentInfo.paymentAmount - totalAmount;

    // Payment amount row
    bytes.addAll(generator.text(
      _padRow('BAYAR', _formatRupiah(paymentInfo.paymentAmount)),
      styles: PosStyles(align: PosAlign.left),
    ));

    if (changeAmount >= 0) {
      bytes.addAll(generator.text(
        _padRow(paymentInfo.isTip ? 'TIP' : 'KEMBALI', _formatRupiah(changeAmount)),
        styles: PosStyles(align: PosAlign.left),
      ));
    } else {
      // Amount still needed row
      bytes.addAll(generator.text(
        _padRow('KURANG', _formatRupiah(changeAmount.abs())),
        styles: PosStyles(align: PosAlign.left),
      ));
    }

    bytes.addAll(generator.text('--------------------------------',
        styles: PosStyles(align: PosAlign.center)));

    return bytes;
  }

  /// Add footer information including disclaimer and thanks
  static List<int> _addFooter(Generator generator, StoreConfig config) {
    List<int> bytes = [];

    // Add disclaimer
    bytes.addAll(generator.feed(1));
    bytes.addAll(_wrapAndAddText(generator, config.disclaimer));

    // Add thank you message
    bytes.addAll(generator.feed(1));
    bytes.addAll(generator.text(config.thankYouText,
        styles: PosStyles(align: PosAlign.center, bold: true)));

    // Add pantun if exists
    if (config.pantunText.isNotEmpty) {
      bytes.addAll(generator.feed(1));
      final pantunLines = config.pantunText.split('\n');
      for (final line in pantunLines) {
        bytes.addAll(
            generator.text(line, styles: PosStyles(align: PosAlign.center)));
      }
    }

    return bytes;
  }

  /// Wrap long text into multiple lines
  static List<int> _wrapAndAddText(Generator generator, String text) {
    List<int> bytes = [];
    try {
      final words = text.split(' ');
      final List<String> wrappedLines = [];
      String currentLine = '';

      for (final word in words) {
        if ((currentLine + word).length > 32) {
          wrappedLines.add(currentLine.trim());
          currentLine = word + ' ';
        } else {
          currentLine += word + ' ';
        }
      }
      if (currentLine.trim().isNotEmpty) {
        wrappedLines.add(currentLine.trim());
      }

      for (final line in wrappedLines) {
        bytes.addAll(
            generator.text(line, styles: PosStyles(align: PosAlign.center)));
      }
    } catch (e) {
      print('Error saat memproses teks: $e');
      // Fallback jika ada error
      bytes.addAll(
          generator.text(text, styles: PosStyles(align: PosAlign.center)));
    }
    return bytes;
  }

  /// Format angka ke rupiah
  static String _formatRupiah(double amount) {
    final formatter = NumberFormat("#,##0", "id_ID");
    return "Rp ${formatter.format(amount)}";
  }

  /// Format angka compact tanpa Rp dan separator
  static String _formatRupiahCompact(double amount) {
    return NumberFormat("0", "id_ID").format(amount);
  }

  /// Format qty x price compact: "2x65000"
  static String _compactQtyPrice(double quantity, double price) {
    final qtyInt = quantity.toInt();
    final priceStr = NumberFormat("0", "id_ID").format(price);
    return "${qtyInt}x$priceStr";
  }

  /// Pad left/right text into one line to avoid ESC a alignment issues in row()
  static String _padRow(String left, String right, {int maxChars = 32}) {
    final padding = maxChars - left.length - right.length;
    return left + ' ' * (padding > 0 ? padding : 1) + right;
  }

  /// Mencetak struk ke printer thermal
  static Future<void> printToThermalPrinter({
    required List<ReceiptGroup> groups,
    required DateTime transactionDate,
    String? destinationAccountName,
    required BuildContext context,
    required dynamic bluetoothService,
    PaymentInfo? paymentInfo,
    String? tipSourceAccountName,
  }) async {
    try {
      // Generate struk dalam format byte array
      final bytes = await generateEscPosBytes(
        groups: groups,
        transactionDate: transactionDate,
        destinationAccountName: destinationAccountName,
        paymentInfo: paymentInfo,
        tipSourceAccountName: tipSourceAccountName,
      );

      // Verifikasi koneksi sebelum mencetak
      final isConnectedBeforePrint = await bluetoothService.checkConnection();
      if (!isConnectedBeforePrint) {
        throw SocketException('Printer tidak terhubung saat akan mencetak');
      }

      try {
        // Konversi List<int> ke Uint8List
        final Uint8List data = Uint8List.fromList(bytes);
        await bluetoothService.printReceipt(data);
      } on SocketException catch (e) {
        PrintErrorHandler.logPrintError('SEND_TO_PRINTER', e);
        throw SocketException('Koneksi ke printer terputus: ${e.message}');
      } on PlatformException catch (e) {
        PrintErrorHandler.logPrintError('SEND_TO_PRINTER', e);
        throw PlatformException(
            code: e.code, message: 'Error printer: ${e.message}');
      } catch (printError) {
        PrintErrorHandler.logPrintError(
            'SEND_TO_PRINTER', printError as Exception);
        throw Exception('Gagal mengirim perintah cetak: $printError');
      }
    } on SocketException {
      rethrow;
    } on PlatformException {
      rethrow;
    } catch (e, stackTrace) {
      PrintErrorHandler.logPrintError(
          'PRINT_THERMAL_PRINTER', e as Exception, stackTrace);
      throw Exception('Gagal mencetak struk: $e');
    }
  }
}
