import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app/core/enum/filter_type.dart';
import 'package:hmg_patient_app/core/enum/viewstate.dart';
import 'package:hmg_patient_app/core/model/labs/LabOrderResult.dart';
import 'package:hmg_patient_app/core/model/labs/lab_result.dart';
import 'package:hmg_patient_app/core/model/labs/patient_lab_orders.dart';
import 'package:hmg_patient_app/core/model/labs/patient_lab_special_result.dart';
import 'package:hmg_patient_app/core/service/medical/labs_service.dart';
import 'package:hmg_patient_app/models/Authentication/authenticated_user.dart';
import 'package:hmg_patient_app/uitl/app_toast.dart';
import 'package:hmg_patient_app/uitl/date_uitl.dart';
import 'package:hmg_patient_app/widgets/charts/app_time_series_chart.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';

import '../../../locator.dart';
import '../base_view_model.dart';

class LabsViewModel extends BaseViewModel {
  FilterType filterType = FilterType.Clinic;
  LabsService _labsService = locator<LabsService>();

  List<LabOrderResult> get labOrdersResultsList => _labsService.labOrdersResultsList;
  List<DataPoint> threePointGraphValue = [];
  List<DataPoint> completeeGraphValues = [];
  List<ThresholdRange> threshold = [];
  double maxYForThreeDots = 0.0;
  double maxYForCompleteGraph = 0.0;

  String get labReportPDF => _labsService.labReportPDF;

  List<PatientLabOrdersList> _patientLabOrdersListClinic = [];
  List<PatientLabOrdersList> _patientLabOrdersListHospital = [];

  List<PatientLabOrdersList> get patientLabOrdersList => filterType == FilterType.Clinic ? _patientLabOrdersListClinic : _patientLabOrdersListHospital;

  void getLabs() async {
    if (authenticatedUserObject.isLogin) {
      setState(ViewState.Busy);
      await _labsService.getPatientLabOrdersList();
      if (_labsService.hasError) {
        error = _labsService.error!;
        setState(ViewState.Error);
      } else {
        _labsService.patientLabOrdersList.forEach((element) {
          List<PatientLabOrdersList> patientLabOrdersClinic = _patientLabOrdersListClinic.where((elementClinic) => elementClinic.filterName == element.clinicDescription).toList();

          if (patientLabOrdersClinic.length != 0) {
            _patientLabOrdersListClinic[_patientLabOrdersListClinic.indexOf(patientLabOrdersClinic[0])].patientLabOrdersList.add(element);
          } else {
            _patientLabOrdersListClinic.add(PatientLabOrdersList(filterName: element.clinicDescription ?? element.projectName, patientDoctorAppointment: element));
          }

          //   doctor list sort via project
          List<PatientLabOrdersList> patientLabOrdersHospital = _patientLabOrdersListHospital
              .where(
                (elementClinic) => elementClinic.filterName == element.projectName,
          )
              .toList();

          if (patientLabOrdersHospital.length != 0) {
            _patientLabOrdersListHospital[_patientLabOrdersListHospital.indexOf(patientLabOrdersHospital[0])].patientLabOrdersList.add(element);
          } else {
            _patientLabOrdersListHospital.add(PatientLabOrdersList(filterName: element.projectName ?? element.clinicDescription, patientDoctorAppointment: element));
          }
        });

        setState(ViewState.Idle);
      }
    }
  }

  setFilterType(FilterType filterType) {
    this.filterType = filterType;
    notifyListeners();
  }

  List<PatientLabSpecialResult> get patientLabSpecialResult => _labsService.patientLabSpecialResult;

  List<LabResult> get labResultList => _labsService.labResultList;

  List<LabResultList> labResultLists = [];

  getLaboratoryResult({String? projectID, int? clinicID, String? invoiceNo, String? invoiceType, String? orderNo, String? setupID, bool? isVidaPlus}) async {
    setState(ViewState.Busy);
    await _labsService.getLaboratoryResult(invoiceNo: invoiceNo, invoiceType: invoiceType, orderNo: orderNo, projectID: projectID, clinicID: clinicID, setupID: setupID, isVidaPlus: isVidaPlus);
    if (_labsService.hasError) {
      error = _labsService.error!;
      // setState(ViewState.Error);
    } else {
      // setState(ViewState.Idle);
    }
  }

  getPatientLabResult({required PatientLabOrders patientLabOrder, required bool isVidaPlus, bool isTablet = false}) async {
    setState(ViewState.Busy);
    await _labsService.getPatientLabResult(patientLabOrder: patientLabOrder, isVidaPlus: isVidaPlus);
    await _labsService.getLaboratoryResult(
        invoiceNo: patientLabOrder.invoiceNo,
        invoiceType: patientLabOrder.invoiceType,
        orderNo: patientLabOrder.orderNo,
        projectID: patientLabOrder.projectID,
        clinicID: patientLabOrder.clinicID,
        setupID: patientLabOrder.setupID,
        isVidaPlus: isVidaPlus);

    if (_labsService.hasError) {
      error = _labsService.error!;
      setState(ViewState.Error);
    } else {
      _labsService.labResultList.forEach((element) {
        List<LabResultList> patientLabOrdersClinic = labResultLists.where((elementClinic) => elementClinic.filterName == element.testCode).toList();
        element.percentage = getPercentage(element.calculatedResultFlag ?? ResultFlag.N, isTablet);
        if (patientLabOrdersClinic.length != 0) {
          labResultLists[labResultLists.indexOf(patientLabOrdersClinic[0])].patientLabResultList!.add(element);
        } else {
          labResultLists.add(LabResultList(filterName: element.testCode, description: element.packageShortDescription, lab: element));
        }
      });
      setState(ViewState.Idle);
    }
  }

  recalculatePercentage(isTablet) {
    for (int i = 0; i < labResultLists.length; i++) {
      for (int j = 0; j < (labResultLists[i].patientLabResultList?.length ?? 0); j++) {
        labResultLists[i].patientLabResultList![j].percentage = getPercentage(labResultLists[i].patientLabResultList![j].calculatedResultFlag ?? ResultFlag.N, isTablet);
      }
    }
  }

  double getPercentage(ResultFlag flag, bool isTablet) {
    double percentage = 0.0;
    var division = (1 / 15);
    switch (flag) {
      case ResultFlag.N:
        percentage = division * (isTablet ? 7.25 : 6.85);
        break;
      case ResultFlag.H:
        percentage = division * (isTablet ? 11 : 10.9);
        break;

      case ResultFlag.L:
        percentage = division * (isTablet ? 3.5 : 2.95);
        break;

      case ResultFlag.CL:
      case ResultFlag.LCL:
        percentage = division * (isTablet ? 0.7 : .04);
        break;

      case ResultFlag.HCH:
      case ResultFlag.CH:
        percentage = division * (isTablet ? 13.5 : 13.3);
        break;
      case ResultFlag.IRR:
        percentage = 0;
        break;
    }

    return percentage * 100;
  }

  getPatientLabOrdersResults({PatientLabOrders? patientLabOrder, String? procedure, bool? isVidaPlus}) async {
    maxYForThreeDots = 0.0;
    setState(ViewState.Busy);
    await _labsService.getPatientLabOrdersResults(
      patientLabOrder: patientLabOrder,
      procedure: procedure,
      isVidaPlus: isVidaPlus!,
    );
    if (_labsService.hasError) {
      error = _labsService.error!;
      setState(ViewState.Error);
    } else {
      /// commented out based on PAP-304
      // bool isShouldClear = false;
      // if (_labsService.labOrdersResultsList.length == 1) {
      //   labOrdersResultsList.forEach((element) {
      //     if (element.resultValue.contains('/') || element.resultValue.contains('*') || element.resultValue.isEmpty)
      //       isShouldClear = true;
      //   });
      // }
      // if (isShouldClear)
      //
      //   _labsService.labOrdersResultsList.clear();

      var sortedResponse = _labsService.sortByFlagAndValue(_labsService.labOrdersResultsList);
      var recentThree = _labsService.getMostRecentThree(sortedResponse);
      var mappedToThresholdValue = _labsService.getThresholdValue();
      // var mappedToThresholdValue = _labsService.buildThresholdList(recentThree);
      // _labsService.adjustLabOrderResults(sortedResponse);
      threshold = mappedToThresholdValue;
      List months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'Jun', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

      double counter = 1;
      recentThree.reversed.forEach((element) {
        try {
          var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
          // debugPrint("the double.parse(element.resultValue!) is ${double.parse(element.resultValue!)}");
          if (double.parse(element.resultValue!) > maxYForThreeDots) {
            maxYForThreeDots = double.parse(element.resultValue!);
          }
          // threePointGraphValue.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
          threePointGraphValue.add(DataPoint( labelValue: counter,value : double.parse(element.resultValue!), actualValue: element.resultValue!,label: formatDateAsMMYY(dateTime), date: dateTime, referenceRangeValue:element.calculatedResultFlag  ??"IRR"));
          counter++;
        } catch (e) {}
      });

      threePointGraphValue.last.label = "thisResult";
      threePointGraphValue.last.isStringResource = true;
      setState(ViewState.Idle);
    }
  }

  /// This function is used to create a full graph data points for the lab results.
  /// @param onComplete: A callback function that is called when the data points are created.
  createFullGraphDatPoints(VoidCallback onComplete) {
    maxYForCompleteGraph = 0.0;
    completeeGraphValues.clear();
    setState(ViewState.Busy);
    double counter = 1;
    threshold =  _labsService.getThresholdValue();
    _labsService.labOrdersResultsList.reversed.forEach((element) {
      try {
        var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
        if(double.parse(element.resultValue!)> maxYForCompleteGraph){
          maxYForCompleteGraph = double.parse(element.resultValue!);
        }
        // completeeGraphValues.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
        completeeGraphValues.add(DataPoint( labelValue: counter,value : double.parse(element.resultValue!), label: formatDateAsMMYY(dateTime), date: dateTime,actualValue: element.resultValue!, referenceRangeValue:element.calculatedResultFlag??"IRR" ));
      } catch (e) {
        print("the mapping is having exception $e");
      }
      ++counter;
    });
    completeeGraphValues.last.label = "thisResult";
    completeeGraphValues.last.isStringResource = true;
    setState(ViewState.Idle);
    onComplete();
  }

  String formatDateAsMMYY(DateTime date) {
    String month = date.month.toString().padLeft(2, '0');
    String year = date.year.toString().substring(2);
    return '$month/$year';
  }
  sendLabReportEmail({PatientLabOrders? patientLabOrder, String? mes, AuthenticatedUser? userObj, required bool isVidaPlus, bool isDownload = false}) async {
    setState(ViewState.Busy);

    await _labsService.sendLabReportEmail(patientLabOrder: patientLabOrder, userObj: userObj, isVidaPlus: isVidaPlus, isDownload: isDownload);
    setState(ViewState.Idle);

    if (_labsService.hasError) {
      error = _labsService.error!;
    } else {
      if (isDownload) {
        if (labReportPDF.isNotEmpty) {
          String path = await _createFileFromString(labReportPDF, "pdf");
          try {
            OpenFilex.open(path);
          } catch (ex) {
            AppToast.showErrorToast(message: "Cannot open file.");
          }
        }
      } else {
        AppToast.showSuccessToast(message: mes!);
      }
    }
  }

  Future<String> _createFileFromString(String encodedStr, String ext) async {
    Uint8List bytes = base64.decode(encodedStr);
    String dir = (await getApplicationDocumentsDirectory()).path;
    File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext);
    await file.writeAsBytes(bytes);
    return file.path;
  }
}
