import 'dart:core';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:intl/intl.dart' show DateFormat;
import 'package:logger/logger.dart';

class LabViewModel extends ChangeNotifier {
  bool isLabOrdersLoading = false;
  bool isLabResultsLoading = false;

  LabRepo labRepo;
  ErrorHandlerService errorHandlerService;
  NavigationService navigationService;

  List<PatientLabOrdersResponseModel> patientLabOrders = [];
  List<PatientLabOrdersResponseModel> filteredLabOrders = [];
  List<PatientLabOrdersResponseModel> tempLabOrdersList = [];

  List<LabResult> mainLabResults = [];
  List<DataPoint> mainGraphPoints = [];
  List<DataPoint> filteredGraphValues = [];
  List months = [
    'Jan',
    'Feb',
    'Mar',
    'April',
    'May',
    'Jun',
    'July',
    'Aug',
    'Sep',
    'Oct',
    'Nov',
    'Dec'
  ];

  late List<String> _labSuggestionsList = [];

  List<String> get labSuggestions => _labSuggestionsList;

   Set<TestDetails> uniqueTests = {};

  double maxY = 0.0;
  double maxX = double.infinity;

  LabViewModel(
      {required this.labRepo,
      required this.errorHandlerService,
      required this.navigationService});

  initLabProvider() {
    patientLabOrders.clear();
    filteredLabOrders.clear();
    isLabOrdersLoading = true;
    isLabResultsLoading = true;
    getPatientLabOrders();
    notifyListeners();
  }

  Future<void> getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async {
    final result = await labRepo.getPatientLabOrders();

    result.fold(
          (failure) async {
            isLabOrdersLoading = false;
            isLabResultsLoading = false;
            notifyListeners();
          },
          // => await errorHandlerService.handleError(failure: failure),
          (apiResponse) {
        if (apiResponse.messageStatus == 2) {
          // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
        } else if (apiResponse.messageStatus == 1) {
          patientLabOrders = apiResponse.data!;
          filteredLabOrders = List.from(patientLabOrders);
          tempLabOrdersList = apiResponse.data!;
          isLabOrdersLoading = false;
          isLabResultsLoading = false;
          filterSuggestions();
          getUniqueTestDescription();
          notifyListeners();
          if (onSuccess != null) {
            onSuccess(apiResponse);
          }
        }
      },
    );
  }

  filterSuggestions() {
    final List<String> labels = patientLabOrders
        .expand((order) => order.testDetails!)
        .map((detail) => detail.description)
        .whereType<String>()
        .toList();
    _labSuggestionsList = labels.toSet().toList();

    notifyListeners();
  }

  filterLabReports(String query) {
    if (query.isEmpty) {
      filteredLabOrders = List.from(patientLabOrders); // reset
    } else {
      filteredLabOrders = patientLabOrders.where((order) {
        final descriptions = order.testDetails?.map((d) => d.description?.toLowerCase()).toList() ?? [];
        return descriptions.any((desc) => desc != null && desc.contains(query.toLowerCase()));
      }).toList();
      patientLabOrders = filteredLabOrders;
    }
    notifyListeners();
  }

  getUniqueTestDescription() {

     uniqueTests = {
      for (var item in patientLabOrders)
        if (item.testDetails != null)
          ...?item.testDetails?.map<TestDetails>((test) => TestDetails(
              description: test.description.toString(),
              testCode: test.testCode.toString(),
              testID: test.testID,
              createdOn: item.createdOn,
              model: item))
    };
  }

  Future<void> getPatientLabResult(
      PatientLabOrdersResponseModel laborder, String procedureName) async {
    LoaderBottomSheet.showLoader();
    mainLabResults.clear();
    filteredGraphValues.clear();
    maxY = double.negativeInfinity;

    final result = await labRepo.getPatientLabResults(
        laborder,
        Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")),
        procedureName);

    result.fold(
      (failure) async {
        LoaderBottomSheet.hideLoader();
        await errorHandlerService.handleError(failure: failure);
      },
      (apiResponse) {
        LoaderBottomSheet.hideLoader();
        if (apiResponse.messageStatus == 2) {
        } else if (apiResponse.messageStatus == 1) {
          var sortedResponse = sortByFlagAndValue(apiResponse.data!);
          var recentThree = sort(sortedResponse);
          mainLabResults = recentThree;

          double counter = 1;
          recentThree.reversed.forEach((element) {
            try {
              var dateTime =
                  DateUtil.convertStringToDate(element.verifiedOnDateTime!);
              var resultValue = double.parse(element.resultValue!);
              var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
              if (resultValue>maxY) {
                maxY = resultValue;
                maxX = maxY;
              }

              filteredGraphValues.add(DataPoint(
                  value: transformedValue,
                  actualValue:element.resultValue!,
                  label: formatDateAsMMYY(dateTime),
               displayTime: resultDate(dateTime),
               time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
                  referenceValue: element.calculatedResultFlag ?? "",

              ));
              counter++;
            } catch (e) {}
          });
          LabResult recentResult = recentThree.first;
          recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!));
          // filteredGraphValues = [filteredGraphValues.first];
          navigationService.push(MaterialPageRoute(
              builder: (_) =>
                  LabResultDetails(recentLabResult: recentResult)));
          notifyListeners();
        }
      },
    );
  }

  String resultDate(DateTime date){


    return '${date.day} ${months[date.month-1]},${date.year}';
  }

  double transformValueInRange(double inputValue, String flag) {
    // Define range boundaries
    double rangeStart, rangeEnd;

    switch (flag) {
      case'LCL':
      case 'CL':
        rangeStart = 0.0;
        rangeEnd = 19.0;
        break;
      case 'L':
        rangeStart = 20.0;
        rangeEnd = 39.0;
        break;
      case 'N':
        rangeStart = 40.0;
        rangeEnd = 59.0;
        break;
      case 'H':
        rangeStart = 60.0;
        rangeEnd = 79.0;
        break;
      case 'HCH':
      case 'CH':
        rangeStart = 80.0;
        rangeEnd = 100.0;
        break;
      default:
        throw ArgumentError('Invalid flag: $flag');
    }

    // Clamp input value to 0-100 and map it to the range bounds
    final clampedValue = inputValue.clamp(0.0, 100.0);
    final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1

    // Map the normalized value to the target range bounds
    final transformedValue = rangeStart + ((normalizedValue * (rangeEnd - rangeStart)));
    debugPrint("the actual value is $inputValue");
    debugPrint("the  flag  is $flag");
    debugPrint("the transformed value is $transformedValue");
    return transformedValue;
  }
  void getSelectedDateRange(DateTime? start, DateTime? end) {
    maxY = double.negativeInfinity;

    if(start == null && end == null) {
      mainLabResults.forEach((element) {
        final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
        try{
          var resultValue = double.parse(element.resultValue!);

          var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
          if (resultValue > maxY) {
            maxY = resultValue;
          }
          filteredGraphValues.add(DataPoint(
            value: transformedValue,
            actualValue: element.resultValue!,
            label: formatDateAsMMYY(time),
            displayTime: resultDate(time),
            time: DateUtil.convertStringToDate(element.verifiedOnDateTime),
            referenceValue: element.calculatedResultFlag ?? "",
          ));
        }catch(e){

        }
      });

    }else {
      filteredGraphValues.clear();

      mainLabResults.forEach((element) {
        try {
          var dateTime =
              DateUtil.convertStringToDate(element.verifiedOnDateTime!);
          var resultValue = double.parse(element.resultValue!);
          var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??"");
          if (resultValue > maxY) {
            maxY = resultValue;
          }
          if (start != null && end == null) {
            if (dateTime.isAtSameMomentAs(start)) {

              filteredGraphValues.add(DataPoint(
                  value: transformedValue,
                  actualValue: element.resultValue!,
                  label: formatDateAsMMYY(dateTime),
                  displayTime: resultDate(dateTime),
                  time:
                      DateUtil.convertStringToDate(element.verifiedOnDateTime),
                  referenceValue: element.calculatedResultFlag ?? ""));
            }
          } else if (start != null && end != null) {
            if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) {

              filteredGraphValues.add(DataPoint(
                  value: transformedValue,
                  actualValue: element.resultValue!,
                  label: formatDateAsMMYY(dateTime),
                  displayTime: resultDate(dateTime),
                  time:
                      DateUtil.convertStringToDate(element.verifiedOnDateTime),
                  referenceValue: element.calculatedResultFlag ?? ""));
            }
          }
        } catch (e) {}
      });
    }
    filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList();


    notifyListeners();
  }

  String formatDateAsMMYY(DateTime date) {

    return '${months[date.month-1]}, ${date.year}';
  }


  List<LabResult> sortByFlagAndValue(List<LabResult> original) {
    const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];

    int getFlagPriority(String? flag) {
      if (flag == null) return priorityOrder.length;
      final index = priorityOrder.indexOf(flag);
      return index == -1 ? priorityOrder.length : index;
    }

    double parseResultValue(String? value) {
      if (value == null) return double.nan;
      return double.tryParse(value) ?? double.nan;
    }

    final copy = List<LabResult>.from(original);
    copy.sort((a, b) {
      final aFlagPriority = getFlagPriority(a.calculatedResultFlag);
      final bFlagPriority = getFlagPriority(b.calculatedResultFlag);

      if (aFlagPriority != bFlagPriority) {
        return aFlagPriority.compareTo(bFlagPriority);
      }

      final aValue = parseResultValue(a.resultValue);
      final bValue = parseResultValue(b.resultValue);

      return aValue.compareTo(bValue);
    });

    return copy;
  }

  List<LabResult> sort(List<LabResult> original) {
    DateTime? parseVerifiedDate(String? raw) {
      if (raw == null) return null;
      final regex = RegExp(r'\/Date\((\d+)\)\/');
      final match = regex.firstMatch(raw);
      if (match != null) {
        final millis = int.tryParse(match.group(1)!);
        if (millis != null) {

          return DateTime.fromMillisecondsSinceEpoch(millis);
        }
      }
      return null;
    }

    final copy = List<LabResult>.from(original);
    copy.sort((a, b) {
      final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime);
      final bDate = DateUtil.convertStringToDate(b.verifiedOnDateTime);
      final now = DateTime.now();
      if (aDate == now && bDate == now) return 0;
      if (aDate == now) return 1;
      if (bDate == now) return -1;
      return bDate.compareTo(aDate); // descending
    });
    return copy.toList();
  }

  List<DataPoint> sortFilteredList(List<DataPoint> original) {


    final copy = List<DataPoint>.from(original);
    copy.sort((a, b) {
      final aDate =a.time;
      final bDate = a.time;
      final now = DateTime.now();
      if (aDate == now && bDate == now) return 0;
      if (aDate == now) return 1;
      if (bDate == now) return -1;
      return bDate.compareTo(aDate); // descending
    });
    return copy.toList();
  }

  Color getColor(String flag) {
    switch (flag) {
      case 'LCL':
        return AppColors.criticalLowAndHigh;
      case 'CL':
        return AppColors.criticalLowAndHigh;
      case 'L':
        return AppColors.highAndLow;
      case 'N':
        return AppColors.bgGreenColor;
      case 'H':
        return AppColors.highAndLow;
      case 'CH':
        return AppColors.criticalLowAndHigh;
      case 'HCH':
        return AppColors.criticalLowAndHigh;
      default:
        return Colors.grey;
    }
  }

  String getFormattedDate(DateTime date){
    return DateFormat('EEEE, dd MMMM. yyyy').format(date);
  }

  String getAssetUrlWRTResult(String refernceValue) {
    switch (refernceValue) {
      case 'CL':
      case 'LCL':
        return AppAssets.ic_critical_low_result;
      case 'L':
        return AppAssets.ic_low_result;
      case 'N':
        return AppAssets.ic_normal_result;
      case 'H':
         return AppAssets.ic_low_result;
      case 'CH':
      case 'HCH':
        return AppAssets.ic_critical_low_result;
      default:
        return AppAssets.ic_normal_result;
    }
  }

  bool getRotationWRTResult(String refernceValue) {
    switch (refernceValue) {
      case 'CL':
      case 'LCL':
      case 'L':
      case 'N':
        return false;
      case 'H':
      case 'CH':
      case 'HCH':
        return true;
      default:
        return true;
    }
  }
}
