import 'dart:io';
import 'dart:typed_data';

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

import 'package:on_field_work/controller/dashboard/dashboard_controller.dart';
import 'package:on_field_work/controller/project_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_image_compressor.dart';
import 'package:on_field_work/helpers/widgets/time_stamp_image_helper.dart';
import 'package:on_field_work/model/attendance/attendance_log_model.dart';
import 'package:on_field_work/model/attendance/attendance_log_view_model.dart';
import 'package:on_field_work/model/attendance/attendance_model.dart';
import 'package:on_field_work/model/attendance/organization_per_project_list_model.dart';
import 'package:on_field_work/model/employees/employee_model.dart';
import 'package:on_field_work/model/project_model.dart';
import 'package:on_field_work/model/regularization_log_model.dart';

class AttendanceController extends GetxController {
  // ------------------ Data Models ------------------
  final List<AttendanceModel> attendances = <AttendanceModel>[];
  final List<ProjectModel> projects = <ProjectModel>[];
  final List<EmployeeModel> employees = <EmployeeModel>[];
  final List<AttendanceLogModel> attendanceLogs = <AttendanceLogModel>[];
  final List<RegularizationLogModel> regularizationLogs =
      <RegularizationLogModel>[];
  final List<AttendanceLogViewModel> attendenceLogsView =
      <AttendanceLogViewModel>[];

  // ------------------ Organizations ------------------
  final List<Organization> organizations = <Organization>[];
  Organization? selectedOrganization;
  final RxBool isLoadingOrganizations = false.obs;

  // ------------------ States ------------------
  String selectedTab = 'todaysAttendance';

  // ✅ Reactive date range
  final Rx<DateTime> startDateAttendance =
      DateTime.now().subtract(const Duration(days: 7)).obs;
  final Rx<DateTime> endDateAttendance =
      DateTime.now().subtract(const Duration(days: 1)).obs;

  final RxBool isLoading = true.obs;
  final RxBool isLoadingProjects = true.obs;
  final RxBool isLoadingEmployees = true.obs;
  final RxBool isLoadingAttendanceLogs = true.obs;
  final RxBool isLoadingRegularizationLogs = true.obs;
  final RxBool isLoadingLogView = true.obs;

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

  final RxBool showPendingOnly = false.obs;
  final RxString searchQuery = ''.obs;

  @override
  void onInit() {
    super.onInit();
    _initializeDefaults();
  }

  void _initializeDefaults() {
    _setDefaultDateRange();
  }

  void _setDefaultDateRange() {
    final DateTime today = DateTime.now();
    startDateAttendance.value = today.subtract(const Duration(days: 7));
    endDateAttendance.value = today.subtract(const Duration(days: 1));
    logSafe(
      'Default date range set: ${startDateAttendance.value} to ${endDateAttendance.value}',
    );
  }

  // ------------------ Computed Filters ------------------
  List<EmployeeModel> get filteredEmployees {
    final String query = searchQuery.value.trim().toLowerCase();
    if (query.isEmpty) return employees;
    return employees
        .where(
          (EmployeeModel e) => e.name.toLowerCase().contains(query),
        )
        .toList();
  }

  List<AttendanceLogModel> get filteredLogs {
    final String query = searchQuery.value.trim().toLowerCase();
    if (query.isEmpty) return attendanceLogs;
    return attendanceLogs
        .where(
          (AttendanceLogModel log) => log.name.toLowerCase().contains(query),
        )
        .toList();
  }

  List<RegularizationLogModel> get filteredRegularizationLogs {
    final String query = searchQuery.value.trim().toLowerCase();
    if (query.isEmpty) return regularizationLogs;
    return regularizationLogs
        .where(
          (RegularizationLogModel log) =>
              log.name.toLowerCase().contains(query),
        )
        .toList();
  }

  // ------------------ Project & Employee APIs ------------------
  Future<void> refreshDataFromNotification({String? projectId}) async {
    projectId ??= Get.find<ProjectController>().selectedProject?.id;
    if (projectId == null) {
      logSafe(
        'No project selected for attendance refresh from notification',
        level: LogLevel.warning,
      );
      return;
    }
    await fetchProjectData(projectId);
    logSafe(
      'Attendance data refreshed from notification for project $projectId',
    );
  }

  Future<void> fetchTodaysAttendance(String? projectId) async {
    if (projectId == null) return;

    isLoadingEmployees.value = true;

    final List<dynamic>? response = await ApiService.getTodaysAttendance(
      projectId,
      organizationId: selectedOrganization?.id,
    );
    if (response != null) {
      employees
        ..clear()
        ..addAll(
          response
              .map<EmployeeModel>(
                (dynamic e) => EmployeeModel.fromJson(
                  e as Map<String, dynamic>,
                ),
              )
              .toList(),
        );

      for (final EmployeeModel emp in employees) {
        uploadingStates[emp.id] = false.obs;
      }

      logSafe(
        'Employees fetched: ${employees.length} for project $projectId',
      );
    } else {
      logSafe(
        'Failed to fetch employees for project $projectId',
        level: LogLevel.error,
      );
    }

    isLoadingEmployees.value = false;
    update();
  }

  Future<void> fetchOrganizations(String projectId) async {
    isLoadingOrganizations.value = true;

    // Keep original return type inference from your ApiService
    final response = await ApiService.getAssignedOrganizations(projectId);

    if (response != null) {
      organizations
        ..clear()
        ..addAll(response.data);
      logSafe('Organizations fetched: ${organizations.length}');
    } else {
      logSafe(
        'Failed to fetch organizations for project $projectId',
        level: LogLevel.error,
      );
    }

    isLoadingOrganizations.value = false;
    update();
  }

  // ------------------ Attendance Capture ------------------
  Future<bool> captureAndUploadAttendance(
    String id,
    String employeeId,
    String projectId, {
    String comment = 'Marked via mobile app',
    required int action,
    bool imageCapture = true,
    String? markTime,
    String? date,
  }) async {
    try {
      _setUploading(employeeId, true);

      final XFile? image = await _captureAndPrepareImage(
        employeeId: employeeId,
        imageCapture: imageCapture,
      );
      if (imageCapture && image == null) {
        return false;
      }

      final Position? position = await _getCurrentPositionSafely();
      if (position == null) return false;

      final String imageName = imageCapture
          ? ApiService.generateImageName(
              employeeId,
              employees.length + 1,
            )
          : '';

      final DateTime effectiveDate =
          _resolveEffectiveDateForAction(action, employeeId);

      final DateTime now = DateTime.now();
      final String formattedMarkTime =
          markTime ?? DateFormat('hh:mm a').format(now);
      final String formattedDate =
          date ?? DateFormat('yyyy-MM-dd').format(effectiveDate);

      final bool result = await ApiService.uploadAttendanceImage(
        id,
        employeeId,
        image,
        position.latitude,
        position.longitude,
        imageName: imageName,
        projectId: projectId,
        comment: comment,
        action: action,
        imageCapture: imageCapture,
        markTime: formattedMarkTime,
        date: formattedDate,
      );

      if (result) {
        logSafe(
          'Attendance uploaded for $employeeId, action: $action, date: $formattedDate',
        );

        if (Get.isRegistered<DashboardController>()) {
          final DashboardController dashboardController =
              Get.find<DashboardController>();
          await dashboardController.fetchTodaysAttendance(projectId);
        }
      }

      return result;
    } catch (e, stacktrace) {
      logSafe(
        'Error uploading attendance',
        level: LogLevel.error,
        error: e,
        stackTrace: stacktrace,
      );
      return false;
    } finally {
      _setUploading(employeeId, false);
    }
  }

  Future<XFile?> _captureAndPrepareImage({
    required String employeeId,
    required bool imageCapture,
  }) async {
    if (!imageCapture) return null;

    final XFile? rawImage = await ImagePicker().pickImage(
      source: ImageSource.camera,
      imageQuality: 80,
    );
    if (rawImage == null) {
      logSafe(
        'Image capture cancelled.',
        level: LogLevel.warning,
      );
      return null;
    }

    final File timestampedFile = await TimestampImageHelper.addTimestamp(
      imageFile: File(rawImage.path),
    );

    final List<int>? compressedBytes =
        await compressImageToUnder100KB(timestampedFile);
    if (compressedBytes == null) {
      logSafe(
        'Image compression failed.',
        level: LogLevel.error,
      );
      return null;
    }

    // FIX: convert List<int> -> Uint8List
    final Uint8List compressedUint8List = Uint8List.fromList(compressedBytes);

    final File compressedFile =
        await saveCompressedImageToFile(compressedUint8List);
    return XFile(compressedFile.path);
  }

  Future<Position?> _getCurrentPositionSafely() async {
    final bool permissionGranted = await _handleLocationPermission();
    if (!permissionGranted) return null;

    return Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high,
    );
  }

  DateTime _resolveEffectiveDateForAction(int action, String employeeId) {
    final DateTime now = DateTime.now();
    if (action != 1) return now;

    final AttendanceLogModel? log = attendanceLogs.firstWhereOrNull(
      (AttendanceLogModel log) =>
          log.employeeId == employeeId && log.checkOut == null,
    );

    return log?.checkIn ?? now;
  }

  void _setUploading(String employeeId, bool value) {
    final RxBool? state = uploadingStates[employeeId];
    if (state != null) {
      state.value = value;
    } else {
      uploadingStates[employeeId] = value.obs;
    }
  }

  Future<bool> _handleLocationPermission() async {
    LocationPermission permission = await Geolocator.checkPermission();

    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        logSafe(
          'Location permissions are denied',
          level: LogLevel.warning,
        );
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      logSafe(
        'Location permissions are permanently denied',
        level: LogLevel.error,
      );
      return false;
    }

    return true;
  }

  // ------------------ Attendance Logs ------------------
  Future<void> fetchAttendanceLogs(
    String? projectId, {
    DateTime? dateFrom,
    DateTime? dateTo,
  }) async {
    if (projectId == null) return;

    isLoadingAttendanceLogs.value = true;

    final List<dynamic>? response = await ApiService.getAttendanceLogs(
      projectId,
      dateFrom: dateFrom,
      dateTo: dateTo,
      organizationId: selectedOrganization?.id,
    );

    if (response != null) {
      attendanceLogs
        ..clear()
        ..addAll(
          response
              .map<AttendanceLogModel>(
                (dynamic e) => AttendanceLogModel.fromJson(
                  e as Map<String, dynamic>,
                ),
              )
              .toList(),
        );
      logSafe('Attendance logs fetched: ${attendanceLogs.length}');
    } else {
      logSafe(
        'Failed to fetch attendance logs for project $projectId',
        level: LogLevel.error,
      );
    }

    isLoadingAttendanceLogs.value = false;
    update();
  }

  Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
    final Map<String, List<AttendanceLogModel>> groupedLogs =
        <String, List<AttendanceLogModel>>{};

    for (final AttendanceLogModel logItem in attendanceLogs) {
      final String checkInDate = logItem.checkIn != null
          ? DateFormat('dd MMM yyyy').format(logItem.checkIn!)
          : 'Unknown';

      groupedLogs.putIfAbsent(
        checkInDate,
        () => <AttendanceLogModel>[],
      )..add(logItem);
    }

    final List<MapEntry<String, List<AttendanceLogModel>>> sortedEntries =
        groupedLogs.entries.toList()
          ..sort(
            (MapEntry<String, List<AttendanceLogModel>> a,
                MapEntry<String, List<AttendanceLogModel>> b) {
              if (a.key == 'Unknown') return 1;
              if (b.key == 'Unknown') return -1;

              final DateTime dateA = DateFormat('dd MMM yyyy').parse(a.key);
              final DateTime dateB = DateFormat('dd MMM yyyy').parse(b.key);
              return dateB.compareTo(dateA);
            },
          );

    return Map<String, List<AttendanceLogModel>>.fromEntries(
      sortedEntries,
    );
  }

  // ------------------ Regularization Logs ------------------
  Future<void> fetchRegularizationLogs(String? projectId) async {
    if (projectId == null) return;

    isLoadingRegularizationLogs.value = true;

    final List<dynamic>? response = await ApiService.getRegularizationLogs(
      projectId,
      organizationId: selectedOrganization?.id,
    );

    if (response != null) {
      regularizationLogs
        ..clear()
        ..addAll(
          response
              .map<RegularizationLogModel>(
                (dynamic e) => RegularizationLogModel.fromJson(
                  e as Map<String, dynamic>,
                ),
              )
              .toList(),
        );
      logSafe(
        'Regularization logs fetched: ${regularizationLogs.length}',
      );
    } else {
      logSafe(
        'Failed to fetch regularization logs for project $projectId',
        level: LogLevel.error,
      );
    }

    isLoadingRegularizationLogs.value = false;
    update();
  }

  // ------------------ Attendance Log View ------------------
  Future<void> fetchLogsView(String? id) async {
    if (id == null) return;

    isLoadingLogView.value = true;

    final List<dynamic>? response = await ApiService.getAttendanceLogView(id);

    if (response != null) {
      attendenceLogsView
        ..clear()
        ..addAll(
          response
              .map<AttendanceLogViewModel>(
                (dynamic e) => AttendanceLogViewModel.fromJson(
                  e as Map<String, dynamic>,
                ),
              )
              .toList(),
        );

      attendenceLogsView.sort(
        (AttendanceLogViewModel a, AttendanceLogViewModel b) =>
            (b.activityTime ?? DateTime(2000))
                .compareTo(a.activityTime ?? DateTime(2000)),
      );

      logSafe('Attendance log view fetched for ID: $id');
    } else {
      logSafe(
        'Failed to fetch attendance log view for ID $id',
        level: LogLevel.error,
      );
    }

    isLoadingLogView.value = false;
    update();
  }

  // ------------------ Combined Load ------------------
  Future<void> loadAttendanceData(String projectId) async {
    isLoading.value = true;
    await fetchProjectData(projectId);
    isLoading.value = false;
  }

  Future<void> fetchProjectData(String? projectId) async {
    if (projectId == null) return;

    await fetchOrganizations(projectId);

    switch (selectedTab) {
      case 'todaysAttendance':
        await fetchTodaysAttendance(projectId);
        break;
      case 'attendanceLogs':
        await fetchAttendanceLogs(
          projectId,
          dateFrom: startDateAttendance.value,
          dateTo: endDateAttendance.value,
        );
        break;
      case 'regularizationRequests':
        await fetchRegularizationLogs(projectId);
        break;
    }

    logSafe(
      'Project data fetched for project ID: $projectId, tab: $selectedTab',
    );
    update();
  }

  // ------------------ UI Interaction ------------------
  Future<void> selectDateRangeForAttendance(
    BuildContext context,
    AttendanceController controller,
  ) async {
    final DateTime today = DateTime.now();

    final DateTimeRange? picked = await showDateRangePicker(
      context: context,
      firstDate: DateTime(2022),
      lastDate: today.subtract(const Duration(days: 1)),
      initialDateRange: DateTimeRange(
        start: startDateAttendance.value,
        end: endDateAttendance.value,
      ),
    );

    if (picked != null) {
      startDateAttendance.value = picked.start;
      endDateAttendance.value = picked.end;

      logSafe(
        'Date range selected: ${startDateAttendance.value} to ${endDateAttendance.value}',
      );

      await controller.fetchAttendanceLogs(
        Get.find<ProjectController>().selectedProject?.id,
        dateFrom: picked.start,
        dateTo: picked.end,
      );
    }
  }
}
