import 'dart:async';

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_habit/provider/ConfigProvider.dart';
import 'package:flutter_habit/common/utils/ConvertUtils.dart';
import 'package:flutter_habit/common/utils/VerificationUtils.dart';
import 'package:flutter_habit/database/entity/BasicInfo.dart';
import 'package:flutter_habit/database/entity/ExerciseInfo.dart';
import 'package:flutter_habit/database/entity/FoodInfo.dart';
import 'package:flutter_habit/database/entity/LifeInfo.dart';
import 'package:flutter_habit/database/entity/ScheduledExercise.dart';
import 'package:flutter_habit/database/entity/SportInfo.dart';
import 'package:flutter_habit/database/entity/StudyInfo.dart';
import 'package:flutter_habit/database/mapper/BasicInfoMapper.dart';
import 'package:flutter_habit/database/mapper/ExerciseInfoMapper.dart';
import 'package:flutter_habit/database/mapper/FoodInfoMapper.dart';
import 'package:flutter_habit/database/mapper/LifeInfoMapper.dart';
import 'package:flutter_habit/database/mapper/ScheduledExerciseMapper.dart';
import 'package:flutter_habit/database/mapper/SportInfoMapper.dart';
import 'package:flutter_habit/database/mapper/StudyInfoMapper.dart';

class DataProvider extends ChangeNotifier {
  Future<void> init() async {
    await loadData();

    DateTime now = DateTime.now();
    Timer.periodic(
        ConvertUtils.dateOfDateTime(now)
            .add(Duration(days: 1, seconds: 1))
            .difference(now), (t) async {
      t.cancel();
      debugPrint("refresh");
      await loadData();
      Timer.periodic(Duration(days: 1), (t) async {
        debugPrint("refresh");
        await loadData();
      });
    });

    debugPrint("init DataProvider");
  }

  Future<void> loadData() async {
    await loadBasicInfoData();
    await loadLifeInfoData();
    await loadExerciseInfoData();
    await loadStudyInfoData();
    await evaluateToday();
  }

  // basic info
  double? height;
  double? weight;
  String? bmi;

  double? breastLine;
  double? waistLine;
  double? hipLine;

  List<FlSpot> weightFlSpots = [];
  int weightChartSize = 7;

  List<FlSpot> brestLineFlSpots = [];
  List<FlSpot> waistLineFlSpots = [];
  List<FlSpot> hipLineFlSpots = [];
  int bwhChartSize = 7;

  Future<void> loadBasicInfoData() async {
    int dateTime90daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
        .subtract(Duration(days: 90))
        .millisecondsSinceEpoch;
    // 基本信息
    List<BasicInfo> basicInfoList =
        (await BasicInfoMapper().selectWhere("date >= $dateTime90daysAgo"))!;
    height = null;
    weight = null;
    bmi = null;
    breastLine = null;
    waistLine = null;
    hipLine = null;
    // 基本数据
    if (basicInfoList.isNotEmpty) {
      height = basicInfoList.last.getHeight();
      weight = basicInfoList.last.getWeight();
      bmi = (weight! / height! / height! * 10000).toStringAsFixed(2);
      breastLine = basicInfoList.last.getBreastLine();
      waistLine = basicInfoList.last.getWaistLine();
      hipLine = basicInfoList.last.getHipLine();
    }
    // 将数据转为点
    weightFlSpots = [];

    brestLineFlSpots = [];
    waistLineFlSpots = [];
    hipLineFlSpots = [];
    basicInfoList.forEach((i) {
      // 换算为x.x天
      weightFlSpots.add(
        FlSpot(
          ConvertUtils.localDaysSinceEpoch(
                  DateTime.fromMillisecondsSinceEpoch(i.getDate()!))
              .floorToDouble(),
          i.getWeight()!,
        ),
      );
      brestLineFlSpots.add(
        FlSpot(
          ConvertUtils.localDaysSinceEpoch(
                  DateTime.fromMillisecondsSinceEpoch(i.getDate()!))
              .floorToDouble(),
          i.getBreastLine()!,
        ),
      );
      waistLineFlSpots.add(
        FlSpot(
          ConvertUtils.localDaysSinceEpoch(
                  DateTime.fromMillisecondsSinceEpoch(i.getDate()!))
              .floorToDouble(),
          i.getWaistLine()!,
        ),
      );
      hipLineFlSpots.add(
        FlSpot(
          ConvertUtils.localDaysSinceEpoch(
                  DateTime.fromMillisecondsSinceEpoch(i.getDate()!))
              .floorToDouble(),
          i.getHipLine()!,
        ),
      );
    });

    notifyListeners();
  }

  // life info
  int? lastNightSleepTime;
  double todayInjectKCal = 0;
  int todayProgress = 0;
  double todayMoney = 0;

  List<FlSpot> sleepTimeFlSpots = [];
  int sleepTimeChartSize = 7;

  List<FlSpot> injectKCalFlSpots = [];
  int injectKCalChartSize = 7;

  List<FlSpot> getUpTimeFlSpots = [];
  List<FlSpot> midRestTimeFlSpots = [];
  List<FlSpot> restTimeFlSpots = [];
  int timeChartSize = 7;

  List<FlSpot> progressFlSpots = [];
  int progressChartSize = 7;

  List<FlSpot> moneyFlSpots = [];
  int moneyChartSize = 7;

  List<FlSpot> eatBreakfastTimeFlSpots = [];
  List<FlSpot> eatLunchTimeFlSpots = [];
  List<FlSpot> eatDinnerTimeFlSpots = [];
  int eatTimeChartSize = 7;

  Future<void> loadLifeInfoData() async {
    int date91daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
        .subtract(Duration(days: 91))
        .millisecondsSinceEpoch;
    // 日常生活
    List<LifeInfo> lifeInfoList =
        (await LifeInfoMapper().selectWhere("date >= $date91daysAgo"))!;
    List<FoodInfo>? foodInfoList = await FoodInfoMapper().selectAll();

    // 构造点、数据
    sleepTimeFlSpots = [];
    injectKCalFlSpots = [];
    progressFlSpots = [];
    getUpTimeFlSpots = [];
    midRestTimeFlSpots = [];
    restTimeFlSpots = [];
    moneyFlSpots = [];
    eatBreakfastTimeFlSpots = [];
    eatLunchTimeFlSpots = [];
    eatDinnerTimeFlSpots = [];
    todayInjectKCal = 0;
    todayProgress = 0;
    todayMoney = 0;
    lastNightSleepTime = null;
    if (lifeInfoList.isNotEmpty) {
      for (int i = 0; i < lifeInfoList.length; i++) {
        LifeInfo lastLifeInfo = i == 0 ? LifeInfo() : lifeInfoList[i - 1];
        LifeInfo currLifeInfo = lifeInfoList[i];
        // 准备数据
        int date = currLifeInfo.getDate()!;
        int? lastDate = lastLifeInfo.getDate();
        int? sleepTime;
        if (lastLifeInfo.getRestTime() != null &&
            currLifeInfo.getGetUpTime() != null) {
          sleepTime = currLifeInfo.getGetUpTime()! - lastLifeInfo.getRestTime()!;
        }
        lastNightSleepTime = sleepTime;
        double totalCal = 0;
        if (currLifeInfo.getBreakfastQuantity() != null) {
          FoodInfo currBreakfastInfo = foodInfoList!.firstWhere(
              (test) => test.getId() == currLifeInfo.getBreakfastId());
          totalCal += currBreakfastInfo.getHgkCalorie()! *
              currLifeInfo.getBreakfastQuantity()!;
        }
        if (currLifeInfo.getLunchQuantity() != null) {
          FoodInfo currLunchInfo = foodInfoList!
              .firstWhere((test) => test.getId() == currLifeInfo.getLunchId());
          totalCal +=
              currLunchInfo.getHgkCalorie()! * currLifeInfo.getLunchQuantity()!;
        }
        if (currLifeInfo.getDinnerQuantity() != null) {
          FoodInfo currDinnerInfo = foodInfoList!
              .firstWhere((test) => test.getId() == currLifeInfo.getDinnerId());
          totalCal +=
              currDinnerInfo.getHgkCalorie()! * currLifeInfo.getDinnerQuantity()!;
        }
        todayInjectKCal = totalCal;
        int progress = 0;
        ConfigProvider configProvider = ConfigProvider();
        configProvider.load();
        progress += currLifeInfo.getGetUpTime() == null
            ? 0
            : VerifyUtils.isBetweenTime(configProvider.getUpTimeStart,
                    currLifeInfo.getGetUpTime()!, configProvider.getUpTimeEnd)
                ? 2
                : 1;
        progress += currLifeInfo.getBreakfastTime() == null
            ? 0
            : VerifyUtils.isBetweenTime(
                    configProvider.breakfastTimeStart,
                    currLifeInfo.getBreakfastTime()!,
                    configProvider.breakfastTimeEnd)
                ? 2
                : 1;
        progress += currLifeInfo.getLunchTime() == null
            ? 0
            : VerifyUtils.isBetweenTime(configProvider.lunchTimeStart,
                    currLifeInfo.getLunchTime()!, configProvider.lunchTimeEnd)
                ? 2
                : 1;
        progress += currLifeInfo.getMidRestTime() == null
            ? 0
            : VerifyUtils.isBetweenTime(
                    configProvider.midRestTimeStart,
                    currLifeInfo.getMidRestTime()!,
                    configProvider.midRestTimeEnd)
                ? 2
                : 1;
        progress += currLifeInfo.getDinnerTime() == null
            ? 0
            : VerifyUtils.isBetweenTime(configProvider.dinnerTimeStart,
                    currLifeInfo.getDinnerTime()!, configProvider.dinnerTimeEnd)
                ? 2
                : 1;
        progress += currLifeInfo.getRestTime() == null
            ? 0
            : VerifyUtils.isBetweenTime(configProvider.restTimeStart,
                    currLifeInfo.getRestTime()!, configProvider.restTimeEnd)
                ? 2
                : 1;
        todayProgress = progress;
        // 构造点
        if (lastDate != null && lastNightSleepTime != null) {
          sleepTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(lastDate))
                  .floorToDouble(),
              ConvertUtils.fixedDouble(
                  ConvertUtils.hourFormMilliseconds(lastNightSleepTime!), 2)));
        }
        injectKCalFlSpots.add(FlSpot(
            ConvertUtils.localDaysSinceEpoch(
                    DateTime.fromMillisecondsSinceEpoch(date))
                .floorToDouble(),
            ConvertUtils.fixedDouble(totalCal, 2)));

        progressFlSpots.add(FlSpot(
            ConvertUtils.localDaysSinceEpoch(
                    DateTime.fromMillisecondsSinceEpoch(date))
                .floorToDouble(),
            ConvertUtils.fixedDouble(progress / 12, 2)));

        if (currLifeInfo.getGetUpTime() != null) {
          getUpTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(date))
                  .floorToDouble(),
              24 -
                  ConvertUtils.hourFormMillisecondsSinceEpoch(
                      currLifeInfo.getGetUpTime()!)));
        }
        if (currLifeInfo.getMidRestTime() != null) {
          midRestTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(date))
                  .floorToDouble(),
              24 -
                  ConvertUtils.hourFormMillisecondsSinceEpoch(
                      currLifeInfo.getMidRestTime()!)));
        }
        if (currLifeInfo.getRestTime() != null) {
          restTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(date))
                  .floorToDouble(),
              24 -
                  ConvertUtils.hourFormMillisecondsSinceEpoch(
                      currLifeInfo.getRestTime()!)));
        }

        if (currLifeInfo.getBreakfastTime() != null) {
          eatBreakfastTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(date))
                  .floorToDouble(),
              24 -
                  ConvertUtils.hourFormMillisecondsSinceEpoch(
                      currLifeInfo.getBreakfastTime()!)));
        }
        if (currLifeInfo.getLunchTime() != null) {
          eatLunchTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(date))
                  .floorToDouble(),
              24 -
                  ConvertUtils.hourFormMillisecondsSinceEpoch(
                      currLifeInfo.getLunchTime()!)));
        }
        if (currLifeInfo.getDinnerTime() != null) {
          eatDinnerTimeFlSpots.add(FlSpot(
              ConvertUtils.localDaysSinceEpoch(
                      DateTime.fromMillisecondsSinceEpoch(date))
                  .floorToDouble(),
              24 -
                  ConvertUtils.hourFormMillisecondsSinceEpoch(
                      currLifeInfo.getDinnerTime()!)));
        }

        double totalMoney = 0;
        totalMoney += currLifeInfo.getBreakfastMoney() ?? 0;
        totalMoney += currLifeInfo.getLunchMoney() ?? 0;
        totalMoney += currLifeInfo.getDinnerMoney() ?? 0;
        moneyFlSpots.add(FlSpot(
            ConvertUtils.localDaysSinceEpoch(
                    DateTime.fromMillisecondsSinceEpoch(date))
                .floorToDouble(),
            ConvertUtils.fixedDouble(totalMoney, 2)));
        todayMoney = totalMoney;
      }
    }
    notifyListeners();
  }

  int sevenDayExerciseTimes = 0;
  double sevenDayExerciseTotalKCal = 0;
  int scheduledExerciseCount = 0;

  List<MapEntry<ScheduledExercise, SportInfo>> scheduledExerciseSportInfoList =
      [];

  List<FlSpot> exerciseInfoFlSpots = [];
  int exerciseInfoChartSize = 7;

  Future<void> loadExerciseInfoData() async {
    int date90daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
        .subtract(Duration(days: 90))
        .millisecondsSinceEpoch;
    int date7daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
        .subtract(Duration(days: 7))
        .millisecondsSinceEpoch;
    List<ExerciseInfo> exerciseInfoList = (await ExerciseInfoMapper()
        .selectWhere("exerciseTime >= $date90daysAgo"))!;
    List<ExerciseInfo> sevenDayExerciseInfoList =
        (await ExerciseInfoMapper().selectWhere("exerciseTime >= $date7daysAgo"))!;
    List<ScheduledExercise> schedules =
        (await ScheduledExerciseMapper().selectAll())!;
    List<SportInfo>? sports = await SportInfoMapper().selectAll();
    scheduledExerciseSportInfoList = schedules.map((i) {
      return MapEntry(
          i, sports!.firstWhere((test) => test.getId() == i.getSportId()));
    }).toList();
    // 卡
    scheduledExerciseCount = schedules.length;
    sevenDayExerciseTimes = sevenDayExerciseInfoList.length;
    sevenDayExerciseTotalKCal = 0;
    sevenDayExerciseInfoList.forEach((i) {
      sevenDayExerciseTotalKCal += i.getExerciseQuantity()! *
          sports!
              .firstWhere((test) => test.getId() == i.getSportId())
              .getHkCalorie()!;
    });
    // 表
    exerciseInfoFlSpots = [];
    Map<int, double> temp = {};
    exerciseInfoList.forEach((i) {
      int currDay = ConvertUtils.localDaysSinceEpoch(
              DateTime.fromMillisecondsSinceEpoch(i.getExerciseTime()!))
          .floor();
      if (temp[currDay] == null) {
        temp[currDay] = i.getExerciseQuantity()! *
            sports!
                .firstWhere((test) => test.getId() == i.getSportId())
                .getHkCalorie()!;
      } else {
        temp[currDay] += i.getExerciseQuantity()! *
            sports!
                .firstWhere((test) => test.getId() == i.getSportId())
                .getHkCalorie()!;
      }
    });
    temp.forEach((k, v) {
      exerciseInfoFlSpots
          .add(FlSpot(k.toDouble(), ConvertUtils.fixedDouble(v, 2)));
    });
    notifyListeners();
  }

  int lateTimes = 0;
  int absentTimes = 0;
  int unSolveTroubles = 0;
  int unDoneHomeWorks = 0;
  List<StudyInfo> unSolveStudyInfoList = [];

  List<FlSpot> dailyStudyCountFlSpots = [];
  int dailyStudyCountChartSize = 7;

  Future<void> loadStudyInfoData() async {
    int date90daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
        .subtract(Duration(days: 90))
        .millisecondsSinceEpoch;
    List<StudyInfo> studyInfoList =
        (await StudyInfoMapper().selectWhere("date >= $date90daysAgo"))!;
    List<StudyInfo> lateList =
        (await StudyInfoMapper().selectWhere("isLate = 1"))!;
    List<StudyInfo> absentList =
        (await StudyInfoMapper().selectWhere("isAbsent = 1"))!;
    List<StudyInfo> unSolveTroublesAndUnDoneHomeList = (await StudyInfoMapper()
        .selectWhere("isTroublesSolved = 0 or isHomeWorkDone == 0"))!;
    lateTimes = 0;
    absentTimes = 0;
    unSolveTroubles = 0;
    unDoneHomeWorks = 0;
    lateTimes = lateList.length;
    absentTimes = absentList.length;
    unSolveStudyInfoList = [];
    unSolveTroubles = 0;
    unDoneHomeWorks = 0;
    unSolveTroublesAndUnDoneHomeList.forEach((i) {
      unSolveStudyInfoList.add(i);
      if (i.getIsHomeWorkDone() == 0) {
        unDoneHomeWorks++;
      }
      if (i.getIsTroublesSolved() == 0) {
        unSolveTroubles++;
      }
    });

    dailyStudyCountFlSpots = [];

    Map<int, int> temp = {};
    studyInfoList.forEach((i) {
      int currDay = ConvertUtils.localDaysSinceEpoch(
              DateTime.fromMillisecondsSinceEpoch(i.getDate()!))
          .floor();
      if (temp[currDay] == null) {
        temp[currDay] = 1;
      } else {
        temp[currDay] += 1;
      }
    });
    temp.forEach((key, value) {
      dailyStudyCountFlSpots.add(FlSpot(key.toDouble(), value.toDouble()));
    });
    notifyListeners();
  }

  int todayEvaluate = 0;

  Future<void> evaluateToday() async {
    // 0 - 15
    todayEvaluate = 0;
    todayEvaluate += todayProgress;
    int todayZeroTime =
        ConvertUtils.dateOfDateTime(DateTime.now()).millisecondsSinceEpoch;
    List<BasicInfo> l1 =
        (await BasicInfoMapper().selectWhere("date >= $todayZeroTime"))!;
    if (l1.isNotEmpty) {
      todayEvaluate++;
    }
    List<ExerciseInfo> l2 =
    (await ExerciseInfoMapper().selectWhere("exerciseTime >= $todayZeroTime"))!;
    if (l2.isNotEmpty) {
      todayEvaluate++;
    }
    List<StudyInfo> l3 =
    (await StudyInfoMapper().selectWhere("date >= $todayZeroTime"))!;
    if (l3.isNotEmpty) {
      todayEvaluate++;
    }
    notifyListeners();
  }
}