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

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

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

class AddExpenseController extends GetxController {
  final amountController = TextEditingController();
  final descriptionController = TextEditingController();
  final supplierController = TextEditingController();
  final transactionIdController = TextEditingController();
  final gstController = TextEditingController();
  final locationController = TextEditingController();
  final transactionDateController = TextEditingController();
  final TextEditingController noOfPersonsController = TextEditingController();

  final RxBool isLoading = false.obs;
  final RxBool isSubmitting = false.obs;
  final RxBool isFetchingLocation = false.obs;

  final Rx<PaymentModeModel?> selectedPaymentMode = Rx<PaymentModeModel?>(null);
  final Rx<ExpenseTypeModel?> selectedExpenseType = Rx<ExpenseTypeModel?>(null);
  final Rx<ExpenseStatusModel?> selectedExpenseStatus = Rx<ExpenseStatusModel?>(null);
  final Rx<EmployeeModel?> selectedPaidBy = Rx<EmployeeModel?>(null);
  final RxString selectedProject = ''.obs;
  final Rx<DateTime?> selectedTransactionDate = Rx<DateTime?>(null);

  final RxList<File> attachments = <File>[].obs;
  final RxList<String> globalProjects = <String>[].obs;
  final RxList<String> projects = <String>[].obs;
  final RxList<ExpenseTypeModel> expenseTypes = <ExpenseTypeModel>[].obs;
  final RxList<PaymentModeModel> paymentModes = <PaymentModeModel>[].obs;
  final RxList<ExpenseStatusModel> expenseStatuses = <ExpenseStatusModel>[].obs;
  final RxList<EmployeeModel> allEmployees = <EmployeeModel>[].obs;

  final RxMap<String, String> projectsMap = <String, String>{}.obs;

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

  @override
  void onInit() {
    super.onInit();
    fetchMasterData();
    fetchGlobalProjects();
    fetchAllEmployees();
  }

  @override
  void onClose() {
    amountController.dispose();
    descriptionController.dispose();
    supplierController.dispose();
    transactionIdController.dispose();
    gstController.dispose();
    locationController.dispose();
    transactionDateController.dispose();
    noOfPersonsController.dispose();
    super.onClose();
  }

  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 && result.paths.isNotEmpty) {
        final files = result.paths.whereType<String>().map((e) => File(e)).toList();
        attachments.addAll(files);
      }
    } catch (e) {
      showAppSnackbar(
        title: "Error",
        message: "Failed to pick attachments: $e",
        type: SnackbarType.error,
      );
    }
  }

  void removeAttachment(File file) {
    attachments.remove(file);
  }

  void pickTransactionDate(BuildContext context) async {
    final now = DateTime.now();
    final picked = await showDatePicker(
      context: context,
      initialDate: selectedTransactionDate.value ?? now,
      firstDate: DateTime(now.year - 5),
      lastDate: now,
    );
    if (picked != null) {
      selectedTransactionDate.value = picked;
      transactionDateController.text =
          "${picked.day.toString().padLeft(2, '0')}-${picked.month.toString().padLeft(2, '0')}-${picked.year}";
    }
  }

  Future<void> fetchCurrentLocation() async {
    isFetchingLocation.value = true;
    try {
      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
        permission = await Geolocator.requestPermission();
        if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
          showAppSnackbar(
            title: "Error",
            message: "Location permission denied. Enable in settings.",
            type: SnackbarType.error,
          );
          return;
        }
      }

      if (!await Geolocator.isLocationServiceEnabled()) {
        showAppSnackbar(
          title: "Error",
          message: "Location services are disabled. Enable them.",
          type: SnackbarType.error,
        );
        return;
      }

      final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
      final placemarks = await placemarkFromCoordinates(position.latitude, position.longitude);

      if (placemarks.isNotEmpty) {
        final place = placemarks.first;
        final address = [
          place.name,
          place.street,
          place.subLocality,
          place.locality,
          place.administrativeArea,
          place.country,
        ].where((e) => e != null && e.isNotEmpty).join(", ");
        locationController.text = address;
      } else {
        locationController.text = "${position.latitude}, ${position.longitude}";
      }
    } catch (e) {
      showAppSnackbar(
        title: "Error",
        message: "Error fetching location: $e",
        type: SnackbarType.error,
      );
    } finally {
      isFetchingLocation.value = false;
    }
  }

  Future<void> submitExpense() async {
    if (isSubmitting.value) return;
    isSubmitting.value = true;

    try {
      List<String> missing = [];

      if (selectedProject.value.isEmpty) missing.add("Project");
      if (selectedExpenseType.value == null) missing.add("Expense Type");
      if (selectedPaymentMode.value == null) missing.add("Payment Mode");
      if (selectedPaidBy.value == null) missing.add("Paid By");
      if (amountController.text.isEmpty) missing.add("Amount");
      if (supplierController.text.isEmpty) missing.add("Supplier Name");
      if (descriptionController.text.isEmpty) missing.add("Description");
      if (attachments.isEmpty) missing.add("Attachments");

      if (missing.isNotEmpty) {
        showAppSnackbar(
          title: "Missing Fields",
          message: "Please provide: ${missing.join(', ')}.",
          type: SnackbarType.error,
        );
        return;
      }

      final amount = double.tryParse(amountController.text);
      if (amount == null) {
        showAppSnackbar(
          title: "Error",
          message: "Please enter a valid amount.",
          type: SnackbarType.error,
        );
        return;
      }

      final selectedDate = selectedTransactionDate.value ?? DateTime.now();
      if (selectedDate.isAfter(DateTime.now())) {
        showAppSnackbar(
          title: "Invalid Date",
          message: "Transaction date cannot be in the future.",
          type: SnackbarType.error,
        );
        return;
      }

      final projectId = projectsMap[selectedProject.value];
      if (projectId == null) {
        showAppSnackbar(
          title: "Error",
          message: "Invalid project selected.",
          type: SnackbarType.error,
        );
        return;
      }

      final billAttachments = await Future.wait(attachments.map((file) async {
        final bytes = await file.readAsBytes();
        final base64 = base64Encode(bytes);
        final mime = lookupMimeType(file.path) ?? 'application/octet-stream';
        final size = await file.length();

        return {
          "fileName": file.path.split('/').last,
          "base64Data": base64,
          "contentType": mime,
          "fileSize": size,
          "description": "",
        };
      }));

      final success = await ApiService.createExpenseApi(
        projectId: projectId,
        expensesTypeId: selectedExpenseType.value!.id,
        paymentModeId: selectedPaymentMode.value!.id,
        paidById: selectedPaidBy.value!.id,
        transactionDate: selectedTransactionDate.value?.toUtc() ?? DateTime.now().toUtc(),
        transactionId: transactionIdController.text,
        description: descriptionController.text,
        location: locationController.text,
        supplerName: supplierController.text,
        amount: amount,
        noOfPersons: selectedExpenseType.value?.noOfPersonsRequired == true
            ? int.tryParse(noOfPersonsController.text.trim()) ?? 0
            : 0,
        billAttachments: billAttachments,
      );

      if (success) {
        await expenseController.fetchExpenses();
        Get.back();
        showAppSnackbar(
          title: "Success",
          message: "Expense created successfully!",
          type: SnackbarType.success,
        );
      } else {
        showAppSnackbar(
          title: "Error",
          message: "Failed to create expense. Try again.",
          type: SnackbarType.error,
        );
      }
    } catch (e) {
      showAppSnackbar(
        title: "Error",
        message: "Something went wrong: $e",
        type: SnackbarType.error,
      );
    } finally {
      isSubmitting.value = false;
    }
  }

  Future<void> fetchMasterData() async {
    try {
      final expenseTypesData = await ApiService.getMasterExpenseTypes();
      final paymentModesData = await ApiService.getMasterPaymentModes();
      final expenseStatusData = await ApiService.getMasterExpenseStatus();

      if (expenseTypesData is List) {
        expenseTypes.value = expenseTypesData.map((e) => ExpenseTypeModel.fromJson(e)).toList();
      }

      if (paymentModesData is List) {
        paymentModes.value = paymentModesData.map((e) => PaymentModeModel.fromJson(e)).toList();
      }

      if (expenseStatusData is List) {
        expenseStatuses.value = expenseStatusData.map((e) => ExpenseStatusModel.fromJson(e)).toList();
      }
    } catch (e) {
      showAppSnackbar(
        title: "Error",
        message: "Failed to fetch master data: $e",
        type: SnackbarType.error,
      );
    }
  }

  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 && name.isNotEmpty) {
            projectsMap[name] = id;
            names.add(name);
          }
        }
        globalProjects.assignAll(names);
        logSafe("Fetched ${names.length} global projects");
      }
    } catch (e) {
      logSafe("Failed to fetch global projects: $e", level: LogLevel.error);
    }
  }

  Future<void> fetchAllEmployees() async {
    isLoading.value = true;
    try {
      final response = await ApiService.getAllEmployees();
      if (response != null && response.isNotEmpty) {
        allEmployees.assignAll(response.map((e) => EmployeeModel.fromJson(e)));
        logSafe("All Employees fetched: ${allEmployees.length}", level: LogLevel.info);
      } else {
        allEmployees.clear();
        logSafe("No employees found.", level: LogLevel.warning);
      }
    } catch (e) {
      allEmployees.clear();
      logSafe("Error fetching employees", level: LogLevel.error, error: e);
    } finally {
      isLoading.value = false;
      update();
    }
  }
}
