import 'dart:convert';
import 'dart:io';
import 'dart:async';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:intl/intl.dart';
import 'package:mime/mime.dart';
import 'package:image_picker/image_picker.dart';

import 'package:on_field_work/controller/expense/expense_screen_controller.dart';
import 'package:on_field_work/helpers/services/api_service.dart';
import 'package:on_field_work/helpers/services/app_logger.dart';
import 'package:on_field_work/helpers/widgets/my_snackbar.dart';
import 'package:on_field_work/model/employees/employee_model.dart';
import 'package:on_field_work/model/expense/expense_type_model.dart';
import 'package:on_field_work/model/expense/payment_types_model.dart';
import 'package:on_field_work/helpers/widgets/time_stamp_image_helper.dart';

class AddExpenseController extends GetxController {
  // --- Text Controllers ---
  final controllers = <TextEditingController>[
    TextEditingController(), // amount
    TextEditingController(), // description
    TextEditingController(), // supplier
    TextEditingController(), // transactionId
    TextEditingController(), // gst
    TextEditingController(), // location
    TextEditingController(), // transactionDate
    TextEditingController(), // noOfPersons
    TextEditingController(), // employeeSearch
  ];

  TextEditingController get amountController => controllers[0];
  TextEditingController get descriptionController => controllers[1];
  TextEditingController get supplierController => controllers[2];
  TextEditingController get transactionIdController => controllers[3];
  TextEditingController get gstController => controllers[4];
  TextEditingController get locationController => controllers[5];
  TextEditingController get transactionDateController => controllers[6];
  TextEditingController get noOfPersonsController => controllers[7];
  TextEditingController get employeeSearchController => controllers[8];

  // --- Reactive State ---
  final isLoading = false.obs;
  final isSubmitting = false.obs;
  final isFetchingLocation = false.obs;
  final isEditMode = false.obs;
  final isSearchingEmployees = false.obs;

// --- Paid By (Single + Multi Selection Support) ---

// single selection
  final selectedPaidBy = Rxn<EmployeeModel>();



// helper setters
  void setSelectedPaidBy(EmployeeModel? emp) {
    selectedPaidBy.value = emp;
  }

  // --- Dropdown Selections & Data ---
  final selectedPaymentMode = Rxn<PaymentModeModel>();
  final selectedExpenseType = Rxn<ExpenseTypeModel>();
  // final selectedPaidBy = Rxn<EmployeeModel>();
  final selectedProject = ''.obs;
  final selectedTransactionDate = Rxn<DateTime>();

  final attachments = <File>[].obs;
  final existingAttachments = <Map<String, dynamic>>[].obs;
  final globalProjects = <String>[].obs;
  final projectsMap = <String, String>{}.obs;

  final expenseTypes = <ExpenseTypeModel>[].obs;
  final paymentModes = <PaymentModeModel>[].obs;
  final allEmployees = <EmployeeModel>[].obs;
  final employeeSearchResults = <EmployeeModel>[].obs;
  final isProcessingAttachment = false.obs;

  String? editingExpenseId;

  final expenseController = Get.find<ExpenseController>();
  final ImagePicker _picker = ImagePicker();

  @override
  void onInit() {
    super.onInit();
    loadMasterData();
    employeeSearchController.addListener(
      () => searchEmployees(employeeSearchController.text),
    );
  }

  @override
  void onClose() {
    for (var c in controllers) {
      c.dispose();
    }
    super.onClose();
  }

  // --- Employee Search ---
  Future<void> searchEmployees(String query) async {
    if (query.trim().isEmpty) return employeeSearchResults.clear();
    isSearchingEmployees.value = true;
    try {
      final data = await ApiService.searchEmployeesBasic(
        searchString: query.trim(),
      );

      if (data is List) {
        employeeSearchResults.assignAll(
          data
              .map((e) => EmployeeModel.fromJson(e as Map<String, dynamic>))
              .toList(),
        );
      } else {
        employeeSearchResults.clear();
      }
    } catch (e) {
      logSafe("Error searching employees: $e", level: LogLevel.error);
      employeeSearchResults.clear();
    } finally {
      isSearchingEmployees.value = false;
    }
  }

  // --- Form Population (Edit) ---
  Future<void> populateFieldsForEdit(Map<String, dynamic> data) async {
    isEditMode.value = true;
    editingExpenseId = '${data['id']}';

    selectedProject.value = data['projectName'] ?? '';
    amountController.text = '${data['amount'] ?? ''}';
    supplierController.text = data['supplerName'] ?? '';
    descriptionController.text = data['description'] ?? '';
    transactionIdController.text = data['transactionId'] ?? '';
    locationController.text = data['location'] ?? '';
    noOfPersonsController.text = '${data['noOfPersons'] ?? 0}';

    _setTransactionDate(data['transactionDate']);
    _setDropdowns(data);
    await _setPaidBy(data);
    _setAttachments(data['attachments']);

    _logPrefilledData();
  }

  void _setTransactionDate(dynamic dateStr) {
    if (dateStr == null) {
      selectedTransactionDate.value = null;
      transactionDateController.clear();
      return;
    }
    try {
      final parsed = DateTime.parse(dateStr);
      selectedTransactionDate.value = parsed;
      transactionDateController.text = DateFormat('dd-MM-yyyy').format(parsed);
    } catch (_) {
      selectedTransactionDate.value = null;
      transactionDateController.clear();
    }
  }

  void _setDropdowns(Map<String, dynamic> data) {
    selectedExpenseType.value =
        expenseTypes.firstWhereOrNull((e) => e.id == data['expensesTypeId']);
    selectedPaymentMode.value =
        paymentModes.firstWhereOrNull((e) => e.id == data['paymentModeId']);
  }

  Future<void> _setPaidBy(Map<String, dynamic> data) async {
    final paidById = '${data['paidById']}';
    selectedPaidBy.value =
        allEmployees.firstWhereOrNull((e) => e.id.trim() == paidById.trim());

    if (selectedPaidBy.value == null && data['paidByFirstName'] != null) {
      await searchEmployees(
        '${data['paidByFirstName']} ${data['paidByLastName']}',
      );
      selectedPaidBy.value = employeeSearchResults
          .firstWhereOrNull((e) => e.id.trim() == paidById.trim());
    }
  }

  void _setAttachments(dynamic attachmentsData) {
    existingAttachments.clear();
    if (attachmentsData is List) {
      existingAttachments.addAll(
        List<Map<String, dynamic>>.from(attachmentsData).map(
          (e) => {...e, 'isActive': true},
        ),
      );
    }
  }

  void _logPrefilledData() {
    final info = [
      'ID: $editingExpenseId',
      'Project: ${selectedProject.value}',
      'Amount: ${amountController.text}',
      'Supplier: ${supplierController.text}',
      'Description: ${descriptionController.text}',
      'Transaction ID: ${transactionIdController.text}',
      'Location: ${locationController.text}',
      'Transaction Date: ${transactionDateController.text}',
      'No. of Persons: ${noOfPersonsController.text}',
      'Expense Category: ${selectedExpenseType.value?.name}',
      'Payment Mode: ${selectedPaymentMode.value?.name}',
      'Paid By: ${selectedPaidBy.value?.name}',
      'Attachments: ${attachments.length}',
      'Existing Attachments: ${existingAttachments.length}',
    ];
    for (var line in info) {
      logSafe(line, level: LogLevel.info);
    }
  }

  // --- Pickers ---
  Future<void> pickTransactionDate(BuildContext context) async {
    final pickedDate = await showDatePicker(
      context: context,
      initialDate: selectedTransactionDate.value ?? DateTime.now(),
      firstDate: DateTime(DateTime.now().year - 5),
      lastDate: DateTime.now(),
    );

    if (pickedDate != null) {
      final now = DateTime.now();
      final finalDateTime = DateTime(
        pickedDate.year,
        pickedDate.month,
        pickedDate.day,
        now.hour,
        now.minute,
        now.second,
      );
      selectedTransactionDate.value = finalDateTime;
      transactionDateController.text =
          DateFormat('dd MMM yyyy').format(finalDateTime);
    }
  }

  Future<void> pickAttachments() async {
    try {
      final result = await FilePicker.platform.pickFiles(
        type: FileType.custom,
        allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'],
        allowMultiple: true,
      );
      if (result != null) {
        attachments.addAll(
          result.paths.whereType<String>().map(File.new),
        );
      }
    } catch (e) {
      _errorSnackbar("Attachment error: $e");
    }
  }

  void removeAttachment(File file) => attachments.remove(file);

  Future<void> pickFromCamera() async {
    try {
      final pickedFile = await _picker.pickImage(source: ImageSource.camera);
      if (pickedFile != null) {
        isProcessingAttachment.value = true; // start loading
        File imageFile = File(pickedFile.path);

        // Add timestamp to the captured image
        File timestampedFile = await TimestampImageHelper.addTimestamp(
          imageFile: imageFile,
        );

        attachments.add(timestampedFile);
        attachments.refresh(); // refresh UI
      }
    } catch (e) {
      _errorSnackbar("Camera error: $e");
    } finally {
      isProcessingAttachment.value = false; // stop loading
    }
  }

  // --- Location ---
  Future<void> fetchCurrentLocation() async {
    isFetchingLocation.value = true;
    try {
      if (!await _ensureLocationPermission()) return;

      final position = await Geolocator.getCurrentPosition();
      final placemarks =
          await placemarkFromCoordinates(position.latitude, position.longitude);

      locationController.text = placemarks.isNotEmpty
          ? [
              placemarks.first.name,
              placemarks.first.street,
              placemarks.first.locality,
              placemarks.first.administrativeArea,
              placemarks.first.country,
            ].where((e) => e?.isNotEmpty == true).join(", ")
          : "${position.latitude}, ${position.longitude}";
    } catch (e) {
      _errorSnackbar("Location error: $e");
    } finally {
      isFetchingLocation.value = false;
    }
  }

  Future<bool> _ensureLocationPermission() async {
    var permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied ||
          permission == LocationPermission.deniedForever) {
        _errorSnackbar("Location permission denied.");
        return false;
      }
    }
    if (!await Geolocator.isLocationServiceEnabled()) {
      _errorSnackbar("Location service disabled.");
      return false;
    }
    return true;
  }

  // --- Data Fetching ---
  Future<void> loadMasterData() async =>
      Future.wait([fetchMasterData(), fetchGlobalProjects()]);

  Future<void> fetchMasterData() async {
    try {
      final types = await ApiService.getMasterExpenseTypes();
      if (types is List) {
        expenseTypes.value = types
            .map((e) => ExpenseTypeModel.fromJson(e as Map<String, dynamic>))
            .toList();
      }

      final modes = await ApiService.getMasterPaymentModes();
      if (modes is List) {
        paymentModes.value = modes
            .map((e) => PaymentModeModel.fromJson(e as Map<String, dynamic>))
            .toList();
      }
    } catch (_) {
      _errorSnackbar("Failed to fetch master data");
    }
  }

  Future<void> fetchGlobalProjects() async {
    try {
      final response = await ApiService.getGlobalProjects();
      if (response != null) {
        final names = <String>[];
        for (var item in response) {
          final name = item['name']?.toString().trim();
          final id = item['id']?.toString().trim();
          if (name != null && id != null) {
            projectsMap[name] = id;
            names.add(name);
          }
        }
        globalProjects.assignAll(names);
      }
    } catch (e) {
      logSafe("Error fetching projects: $e", level: LogLevel.error);
    }
  }

  // --- Submission ---
  Future<void> submitOrUpdateExpense() async {
    if (isSubmitting.value) return;
    isSubmitting.value = true;
    try {
      final validationMsg = validateForm();
      if (validationMsg.isNotEmpty) {
        _errorSnackbar(validationMsg, "Missing Fields");
        return;
      }

      final payload = await _buildExpensePayload();
      final success = await _submitToApi(payload);

      if (success) {
        await expenseController.fetchExpenses();
        Get.back();
        showAppSnackbar(
          title: "Success",
          message:
              "Expense ${isEditMode.value ? 'updated' : 'created'} successfully!",
          type: SnackbarType.success,
        );
      } else {
        _errorSnackbar("Operation failed. Try again.");
      }
    } catch (e) {
      _errorSnackbar("Unexpected error: $e");
    } finally {
      isSubmitting.value = false;
    }
  }

  Future<bool> _submitToApi(Map<String, dynamic>? payload) async {
    if (payload == null) {
      _errorSnackbar("Payload is empty. Cannot submit.");
      return false;
    }

    try {
      if (isEditMode.value && editingExpenseId != null) {
        // Edit existing expense
        return await ApiService.editExpenseApi(
          expenseId: editingExpenseId!,
          payload: payload,
        );
      } else {
        // Create new expense
        return await ApiService.createExpenseApi(
          projectId: payload['projectId'],
          expensesTypeId: payload['expenseCategoryId'],
          paymentModeId: payload['paymentModeId'],
          paidById: payload['paidById'],
          transactionDate: DateTime.parse(payload['transactionDate']),
          transactionId: payload['transactionId'],
          description: payload['description'],
          location: payload['location'],
          supplerName: payload['supplerName'],
          amount: payload['amount'],
          noOfPersons: payload['noOfPersons'],
          billAttachments: payload['billAttachments'],
        );
      }
    } catch (e) {
      _errorSnackbar("Failed to submit expense: $e");
      return false;
    }
  }

  Future<Map<String, dynamic>?> _buildExpensePayload() async {
    final now = DateTime.now();

    // --- Get IDs safely ---
    final projectId = projectsMap[selectedProject.value];
    final expenseType = selectedExpenseType.value;
    final paymentMode = selectedPaymentMode.value;
    final paidBy = selectedPaidBy.value;

    // --- Validate essential fields ---
    if (projectId == null) {
      _errorSnackbar("Project not selected or invalid");
      return null;
    }
    if (expenseType == null) {
      _errorSnackbar("Expense Category not selected");
      return null;
    }
    if (paymentMode == null) {
      _errorSnackbar("Payment mode not selected");
      return null;
    }
    if (paidBy == null) {
      _errorSnackbar("Paid By not selected");
      return null;
    }

    // --- Process existing attachments (for edit mode) ---
    final existingPayload = isEditMode.value
        ? existingAttachments
            .map((e) => {
                  "documentId": e['documentId'],
                  "fileName": e['fileName'] ?? "",
                  "contentType": e['contentType'] ?? "",
                  "fileSize": 0,
                  "description": "",
                  "url": e['url'] ?? "",
                  "isActive": e['isActive'] ?? true,
                  "base64Data": "",
                })
            .toList()
        : <Map<String, dynamic>>[];

    // --- Process new attachments ---
    final newPayload = await Future.wait(
      attachments.map((file) async {
        final bytes = await file.readAsBytes();
        return {
          "fileName": file.path.split('/').last,
          "base64Data": base64Encode(bytes),
          "contentType":
              lookupMimeType(file.path) ?? 'application/octet-stream',
          "fileSize": await file.length(),
          "description": "",
        };
      }),
    );

    // --- Build final payload ---
    final payload = {
      if (isEditMode.value && editingExpenseId != null) "id": editingExpenseId,
      "projectId": projectId,
      "expenseCategoryId": expenseType.id,
      "paymentModeId": paymentMode.id,
      "paidById": paidBy.id,
      "transactionDate":
          (selectedTransactionDate.value ?? now).toUtc().toIso8601String(),
      "transactionId": transactionIdController.text.trim(),
      "description": descriptionController.text.trim(),
      "location": locationController.text.trim(),
      "supplerName": supplierController.text.trim(),
      "amount": double.tryParse(amountController.text.trim()) ?? 0,
      "noOfPersons": expenseType.noOfPersonsRequired == true
          ? int.tryParse(noOfPersonsController.text.trim()) ?? 0
          : 0,
      "billAttachments": [...existingPayload, ...newPayload].isEmpty
          ? null
          : [...existingPayload, ...newPayload],
    };

    return payload;
  }

  String validateForm() {
    final missing = <String>[];

    if (selectedProject.value.isEmpty) missing.add("Project");
    if (selectedExpenseType.value == null) missing.add("Expense Category");
    if (selectedPaymentMode.value == null) missing.add("Payment Mode");
    if (selectedPaidBy.value == null) missing.add("Paid By");
    if (amountController.text.trim().isEmpty) missing.add("Amount");
    if (descriptionController.text.trim().isEmpty) missing.add("Description");

    if (selectedTransactionDate.value == null) {
      missing.add("Transaction Date");
    } else if (selectedTransactionDate.value!.isAfter(DateTime.now())) {
      missing.add("Valid Transaction Date");
    }

    if (double.tryParse(amountController.text.trim()) == null) {
      missing.add("Valid Amount");
    }

    final hasActiveExisting =
        existingAttachments.any((e) => e['isActive'] != false);
    if (attachments.isEmpty && !hasActiveExisting) {
      missing.add("Attachment");
    }

    return missing.isEmpty ? '' : "Please provide: ${missing.join(', ')}.";
  }

  // --- Snackbar Helper ---
  void _errorSnackbar(String msg, [String title = "Error"]) {
    showAppSnackbar(title: title, message: msg, type: SnackbarType.error);
  }
}
