import 'package:get/get.dart';
import 'package:on_field_work/helpers/services/app_logger.dart';
import 'package:on_field_work/helpers/services/api_service.dart';
import 'package:on_field_work/helpers/widgets/my_form_validator.dart';
import 'package:on_field_work/helpers/widgets/my_snackbar.dart';
import 'package:on_field_work/model/project_model.dart';
import 'package:on_field_work/model/dailyTaskPlanning/daily_task_planning_model.dart';
import 'package:on_field_work/model/employees/employee_model.dart';

class DailyTaskPlanningController extends GetxController {
  List<ProjectModel> projects = [];
  RxList<EmployeeModel> employees = <EmployeeModel>[].obs;
  RxList<EmployeeModel> selectedEmployees = <EmployeeModel>[].obs;
  List<EmployeeModel> allEmployeesCache = [];
  List<TaskPlanningDetailsModel> dailyTasks = [];
  RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;

  MyFormValidator basicValidator = MyFormValidator();
  List<Map<String, dynamic>> roles = [];
  RxBool isAssigningTask = false.obs;
  RxnString selectedRoleId = RxnString();
  RxBool isFetchingTasks = true.obs;
  RxBool isFetchingProjects = true.obs;
  RxBool isFetchingEmployees = true.obs;

  /// New: track per-building loading and loaded state for lazy infra loading
  RxMap<String, RxBool> buildingLoadingStates = <String, RxBool>{}.obs;
  final Set<String> buildingsWithDetails = <String>{};

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

  String? formFieldValidator(String? value, {required String fieldType}) {
    if (value == null || value.trim().isEmpty) return 'This field is required';
    if (fieldType == "target" && int.tryParse(value.trim()) == null) {
      return 'Please enter a valid number';
    }
    if (fieldType == "description" && value.trim().length < 5) {
      return 'Description must be at least 5 characters';
    }
    return null;
  }

  void updateSelectedEmployees() {
    selectedEmployees.value =
        employees.where((e) => uploadingStates[e.id]?.value == true).toList();
    logSafe("Updated selected employees", level: LogLevel.debug);
  }

  void onRoleSelected(String? roleId) {
    selectedRoleId.value = roleId;
    logSafe("Role selected", level: LogLevel.info);
  }

  Future<void> fetchRoles() async {
    logSafe("Fetching roles...", level: LogLevel.info);
    final result = await ApiService.getRoles();
    if (result != null) {
      roles = List<Map<String, dynamic>>.from(result);
      logSafe("Roles fetched successfully", level: LogLevel.info);
      update();
    } else {
      logSafe("Failed to fetch roles", level: LogLevel.error);
    }
  }

  Future<bool> assignDailyTask({
    required String workItemId,
    required int plannedTask,
    required String description,
    required List<String> taskTeam,
    DateTime? assignmentDate,
    String? organizationId,
    String? serviceId,
  }) async {
    isAssigningTask.value = true;
    logSafe("Starting assign task...", level: LogLevel.info);

    final response = await ApiService.assignDailyTask(
      workItemId: workItemId,
      plannedTask: plannedTask,
      description: description,
      taskTeam: taskTeam,
      assignmentDate: assignmentDate,
      organizationId: organizationId,
      serviceId: serviceId,
    );

    isAssigningTask.value = false;

    if (response == true) {
      logSafe("Task assigned successfully", level: LogLevel.info);
      showAppSnackbar(
        title: "Success",
        message: "Task assigned successfully!",
        type: SnackbarType.success,
      );
      return true;
    } else {
      logSafe("Failed to assign task", level: LogLevel.error);
      showAppSnackbar(
        title: "Error",
        message: "Failed to assign task.",
        type: SnackbarType.error,
      );
      return false;
    }
  }

  /// Fetch buildings list only (no deep area/workItem calls) for initial load.
  Future<void> fetchTaskData(String? projectId, {String? serviceId}) async {
    if (projectId == null) return;

    isFetchingTasks.value = true;
    try {
      final infraResponse = await ApiService.getInfraDetails(
        projectId,
        serviceId: serviceId,
      );
      final infraData = infraResponse?['data'] as List<dynamic>?;

      if (infraData == null || infraData.isEmpty) {
        dailyTasks = [];
        return;
      }

      // Filter buildings with 0 planned & completed work
      final filteredBuildings = infraData.where((b) {
        final planned = (b['plannedWork'] as num?)?.toDouble() ?? 0;
        final completed = (b['completedWork'] as num?)?.toDouble() ?? 0;
        return planned > 0 || completed > 0;
      }).toList();

      dailyTasks = filteredBuildings.map((buildingJson) {
        final building = Building(
          id: buildingJson['id'],
          name: buildingJson['buildingName'],
          description: buildingJson['description'],
          floors: [],
          plannedWork: (buildingJson['plannedWork'] as num?)?.toDouble() ?? 0,
          completedWork:
              (buildingJson['completedWork'] as num?)?.toDouble() ?? 0,
        );

        return TaskPlanningDetailsModel(
          id: building.id,
          name: building.name,
          projectAddress: "",
          contactPerson: "",
          startDate: DateTime.now(),
          endDate: DateTime.now(),
          projectStatusId: "",
          buildings: [building],
        );
      }).toList();

      buildingLoadingStates.clear();
      buildingsWithDetails.clear();
    } catch (e, stack) {
      logSafe("Error fetching daily task data",
          level: LogLevel.error, error: e, stackTrace: stack);
    } finally {
      isFetchingTasks.value = false;
      update();
    }
  }

  /// Fetch full infra for a single building (floors, workAreas, workItems).
  /// Called lazily when user expands a building in the UI.
  Future<void> fetchBuildingInfra(
      String buildingId, String projectId, String? serviceId) async {
    if (buildingId.isEmpty) return;

    // mark loading
    buildingLoadingStates.putIfAbsent(buildingId, () => true.obs);
    buildingLoadingStates[buildingId]!.value = true;
    update();

    try {
      // Re-use getInfraDetails and find the building entry for the requested buildingId
      final infraResponse =
          await ApiService.getInfraDetails(projectId, serviceId: serviceId);
      final infraData = infraResponse?['data'] as List<dynamic>? ?? [];

      final buildingJson = infraData.firstWhere(
        (b) => b['id'].toString() == buildingId.toString(),
        orElse: () => null,
      );

      if (buildingJson == null) {
        logSafe("Building $buildingId not found in infra response",
            level: LogLevel.warning);
        return;
      }

      // Build floors & workAreas for this building
      final building = Building(
        id: buildingJson['id'],
        name: buildingJson['buildingName'],
        description: buildingJson['description'],
        floors:
            (buildingJson['floors'] as List<dynamic>? ?? []).map((floorJson) {
          return Floor(
            id: floorJson['id'],
            floorName: floorJson['floorName'],
            workAreas: (floorJson['workAreas'] as List<dynamic>? ?? [])
                .map((areaJson) {
              return WorkArea(
                id: areaJson['id'],
                areaName: areaJson['areaName'],
                workItems: [], // will populate later
              );
            }).toList(),
          );
        }).toList(),
        plannedWork: (buildingJson['plannedWork'] as num?)?.toDouble() ?? 0,
        completedWork: (buildingJson['completedWork'] as num?)?.toDouble() ?? 0,
      );

      // For each workArea, fetch its work items and populate
      await Future.wait(
          building.floors.expand((f) => f.workAreas).map((area) async {
        try {
          final taskResponse = await ApiService.getWorkItemsByWorkArea(area.id,
              serviceId: serviceId);
          final taskData = taskResponse?['data'] as List<dynamic>? ?? [];
          area.workItems.addAll(taskData.map((taskJson) => WorkItemWrapper(
                workItemId: taskJson['id'],
                workItem: WorkItem(
                  id: taskJson['id'],
                  activityMaster: taskJson['activityMaster'] != null
                      ? ActivityMaster.fromJson(taskJson['activityMaster'])
                      : null,
                  workCategoryMaster: taskJson['workCategoryMaster'] != null
                      ? WorkCategoryMaster.fromJson(
                          taskJson['workCategoryMaster'])
                      : null,
                  plannedWork: (taskJson['plannedWork'] as num?)?.toDouble(),
                  completedWork:
                      (taskJson['completedWork'] as num?)?.toDouble(),
                  todaysAssigned:
                      (taskJson['todaysAssigned'] as num?)?.toDouble(),
                  description: taskJson['description'] as String?,
                  taskDate: taskJson['taskDate'] != null
                      ? DateTime.tryParse(taskJson['taskDate'])
                      : null,
                ),
              )));
        } catch (e, stack) {
          logSafe("Error fetching tasks for work area ${area.id}",
              level: LogLevel.error, error: e, stackTrace: stack);
        }
      }));

      // Merge/replace the building into dailyTasks
      bool merged = false;
      for (var t in dailyTasks) {
        final idx = t.buildings
            .indexWhere((b) => b.id.toString() == building.id.toString());
        if (idx != -1) {
          t.buildings[idx] = building;
          merged = true;
          break;
        }
      }
      if (!merged) {
        // If not present, add a new TaskPlanningDetailsModel wrapper (fallback)
        dailyTasks.add(TaskPlanningDetailsModel(
          id: building.id,
          name: building.name,
          projectAddress: "",
          contactPerson: "",
          startDate: DateTime.now(),
          endDate: DateTime.now(),
          projectStatusId: "",
          buildings: [building],
        ));
      }

      // Mark as loaded
      buildingsWithDetails.add(buildingId.toString());
    } catch (e, stack) {
      logSafe("Error fetching infra for building $buildingId",
          level: LogLevel.error, error: e, stackTrace: stack);
    } finally {
      buildingLoadingStates.putIfAbsent(buildingId, () => false.obs);
      buildingLoadingStates[buildingId]!.value = false;
      update();
    }
  }

  Future<void> fetchEmployeesByProjectService({
    required String projectId,
    String? serviceId,
    String? organizationId,
  }) async {
    isFetchingEmployees.value = true;

    try {
      final response = await ApiService.getEmployeesByProjectService(
        projectId,
        serviceId: serviceId ?? '',
        organizationId: organizationId ?? '',
      );

      if (response != null && response.isNotEmpty) {
        employees
            .assignAll(response.map((json) => EmployeeModel.fromJson(json)));

        if (serviceId == null && organizationId == null) {
          allEmployeesCache = List.from(employees);
        }

        final currentEmployeeIds = employees.map((e) => e.id).toSet();

        uploadingStates
            .removeWhere((key, _) => !currentEmployeeIds.contains(key));
        employees.forEach((emp) {
          uploadingStates.putIfAbsent(emp.id, () => false.obs);
        });

        selectedEmployees
            .removeWhere((e) => !currentEmployeeIds.contains(e.id));

        logSafe("Employees fetched: ${employees.length}", level: LogLevel.info);
      } else {
        employees.clear();
        uploadingStates.clear();
        selectedEmployees.clear();
        logSafe(
          serviceId != null || organizationId != null
              ? "Filtered employees empty"
              : "No employees found",
          level: LogLevel.warning,
        );
      }
    } catch (e, stack) {
      logSafe("Error fetching employees",
          level: LogLevel.error, error: e, stackTrace: stack);

      if (serviceId == null &&
          organizationId == null &&
          allEmployeesCache.isNotEmpty) {
        employees.assignAll(allEmployeesCache);

        final cachedEmployeeIds = employees.map((e) => e.id).toSet();
        uploadingStates
            .removeWhere((key, _) => !cachedEmployeeIds.contains(key));
        employees.forEach((emp) {
          uploadingStates.putIfAbsent(emp.id, () => false.obs);
        });

        selectedEmployees.removeWhere((e) => !cachedEmployeeIds.contains(e.id));
      } else {
        employees.clear();
        uploadingStates.clear();
        selectedEmployees.clear();
      }
    } finally {
      isFetchingEmployees.value = false;
      update();
    }
  }
}
