import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:diameter/components/forms/date_time_form_field.dart';
import 'package:diameter/localization_keys.dart';
import 'package:diameter/models/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/screens/reports/daily_chart.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:intl/intl.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';

class MealData {
  int id;
  String text;
  String portion;
  String carbs;
  String notes;
  String boli;

  MealData({
    this.id = 0,
    this.text = '',
    this.portion = '',
    this.carbs = '',
    this.notes = '',
    this.boli = '',
  });
}

class ExportDialog extends StatefulWidget {
  static const String routeName = '/export';

  final DateTime? date;

  const ExportDialog({Key? key, this.date}) : super(key: key);

  @override
  _ExportDialogState createState() => _ExportDialogState();
}

class _ExportDialogState extends State<ExportDialog> {
  final ScrollController _scrollController = ScrollController();

  late DateTime exportStartDate;
  late DateTime exportEndDate;

  TextEditingController exportStartDateController =
      TextEditingController(text: '');
  TextEditingController exportEndDateController =
      TextEditingController(text: '');

  bool _isSaving = false;
  bool exportRange = false;
  bool showChart = true;
  bool showBolus = true;
  bool showBasal = true;
  bool showMeals = true;
  bool showTable = true;

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    exportStartDate = widget.date ?? DateTimeUtils.today();
    exportEndDate = DateTimeUtils.today();

    exportStartDateController.text = DateTimeUtils.displayDate(exportStartDate);
    exportEndDateController.text = DateTimeUtils.displayDate(exportEndDate);
  }

  void onExport(BuildContext context) async {
    setState(() {
      _isSaving = true;
    });
    final bytes = await generateLogReport(
      startDate: exportStartDate,
      endDate: exportRange ? exportEndDate : exportStartDate,
      showChart: showChart,
      showTable: showTable,
      showMeals: showMeals,
      showBasal: showBasal,
      showBolus: showBolus,
    );
    final appDocDir = await getApplicationDocumentsDirectory();
    final appDocPath = appDocDir.path;

    final DateFormat formatter = DateFormat(DateFormat.YEAR_MONTH_DAY);
    final file = File(
        '$appDocPath/diameter_${exportRange ? '${formatter.format(exportStartDate)}-${formatter.format(exportEndDate)}' : formatter.format(exportStartDate)}.pdf');
    await file.writeAsBytes(bytes);
    await OpenFile.open(file.path);
    Navigator.pop(context);
    setState(() {
      _isSaving = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Row(
              children: [
                Expanded(
                  child: RadioListTile(
                      title: Text(translate(LocalizationKeys.reports_export_singleDate)),
                      groupValue: exportRange,
                      value: false,
                      onChanged: (_) {
                        setState(() {
                          exportRange = false;
                        });
                      }),
                ),
                Expanded(
                  child: RadioListTile(
                      title: Text(translate(LocalizationKeys.reports_export_range)),
                      groupValue: exportRange,
                      value: true,
                      onChanged: (value) {
                        setState(() {
                          exportRange = true;
                        });
                      }),
                ),
              ],
            ),
            Row(
              children: [
                Expanded(
                  child: DateTimeFormField(
                    date: exportStartDate,
                    label: translate(LocalizationKeys.reports_export_date),
                    controller: exportStartDateController,
                    onChanged: (newDate) {
                      if (newDate != null) {
                        exportStartDate =
                            DateTime(newDate.year, newDate.month, newDate.day);
                        exportStartDateController.text =
                            DateTimeUtils.displayDate(exportStartDate);
                      }
                    },
                  ),
                ),
                exportRange
                    ? Expanded(
                        child: Padding(
                          padding: const EdgeInsets.only(left: 5.0),
                          child: DateTimeFormField(
                            date: exportEndDate,
                            label: translate(LocalizationKeys.reports_export_endDate),
                            controller: exportEndDateController,
                            onChanged: (newDate) {
                              if (newDate != null) {
                                exportEndDate = DateTime(
                                    newDate.year, newDate.month, newDate.day);
                                exportEndDateController.text =
                                    DateTimeUtils.displayDate(exportEndDate);
                              }
                            },
                          ),
                        ),
                      )
                    : Container(),
              ],
            ),
            CheckboxListTile(
              value: showChart,
              onChanged: (_) => setState(() => showChart = !showChart),
              title: Text(translate(LocalizationKeys.reports_export_showChart)),
              controlAffinity: ListTileControlAffinity.leading,
            ),
            Padding(
              padding: const EdgeInsets.only(left: 20.0),
              child: CheckboxListTile(
                value: showBolus,
                onChanged: showChart
                    ? (_) => setState(() => showBolus = !showBolus)
                    : null,
                title: Text(translate(LocalizationKeys.reports_export_showBolus)),
                controlAffinity: ListTileControlAffinity.leading,
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 20.0),
              child: CheckboxListTile(
                value: showBasal,
                onChanged: showChart
                    ? (_) => setState(() => showBasal = !showBasal)
                    : null,
                title: Text(translate(LocalizationKeys.reports_export_showBasal)),
                controlAffinity: ListTileControlAffinity.leading,
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 20.0),
              child: CheckboxListTile(
                value: showMeals,
                onChanged: showChart
                    ? (_) => setState(() => showMeals = !showMeals)
                    : null,
                title: Text(translate(LocalizationKeys.reports_export_showMeals)),
                controlAffinity: ListTileControlAffinity.leading,
              ),
            ),
            CheckboxListTile(
              value: showTable,
              onChanged: (_) => setState(() => showTable = !showTable),
              title: Text(translate(LocalizationKeys.reports_export_showTable)),
              controlAffinity: ListTileControlAffinity.leading,
            ),
          ],
        ),
        actions: <Widget>[
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text(translate(LocalizationKeys.general_cancel).toUpperCase()),
          ),
          ElevatedButton(
            onPressed: !_isSaving && (showChart || showTable)
                ? () => onExport(context)
                : null,
            child: Text(translate(LocalizationKeys.reports_export_export).toUpperCase()),
          ),
        ]);
  }
}

Future<pw.Image> generateChartAsImage(
    DateTime date,
    List<LogEntry> logEntries,
    List<LogEvent> logEvents,
    List<GlucoseTarget> targets,
    bool showMeals,
    bool showBasal,
    bool showBolus) async {
  final chart = generateChart(
      date, logEntries, logEvents, targets, showMeals, showBasal, showBolus);

  return pw.Image(
    await WidgetWraper.fromWidget(
      widget: chart,
      constraints: BoxConstraints(
        minWidth: PdfPageFormat.a4.landscape.availableWidth,
        maxWidth: PdfPageFormat.a4.landscape.availableWidth,
        minHeight: PdfPageFormat.a4.landscape.availableHeight / 2,
        maxHeight: PdfPageFormat.a4.landscape.availableHeight,
      ),
    ),
  );
}

pw.Table generateTable(List<LogEntry> logEntries) {
  const baseColor = PdfColors.grey;
  const small = pw.TextStyle(fontSize: 8.0);

  final tableHeaders = [
    translate(LocalizationKeys.reports_export_tableHeaders_time),
    translate(LocalizationKeys.reports_export_tableHeaders_glucose),
    translate(LocalizationKeys.reports_export_tableHeaders_notes),
    translate(LocalizationKeys.reports_export_tableHeaders_meals),
    translate(LocalizationKeys.reports_export_tableHeaders_portionSize),
    translate(LocalizationKeys.reports_export_tableHeaders_carbs),
    translate(LocalizationKeys.reports_export_tableHeaders_mealBolus),
    translate(LocalizationKeys.reports_export_tableHeaders_mealNotes),
  ];

  final List<pw.TableRow> data = logEntries.map((logEntry) {
    pw.TextStyle glucoseStyle = pw.TextStyle(
      color: PdfColor.fromInt(GlucoseTarget.getColorForGlucose(
              mgPerDl: logEntry.mgPerDl ?? 0, mmolPerL: logEntry.mmolPerL ?? 0)
          .value),
    );

    final boli = LogBolus.getAllForEntry(logEntry.id);
    final meals = LogMeal.getAllForEntry(logEntry.id)
        .map((meal) => MealData(
              id: meal.id,
              text: meal.value,
              portion:
                  '${Utils.getFractionDigitsLength(meal.amount) == 0 ? meal.amount.toInt().toString() : meal.amount} ${meal.mealPortionType.target?.value}',
              carbs: Utils.displayNutritionAmount(meal.totalCarbs ?? 0,
                  cutExtraDigits: true),
              boli: [
                for (var bolus in boli
                    .where((bolus) => bolus.meal.targetId == meal.id)
                    .toList())
                  '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)} ${bolus.delay != null ? translate(LocalizationKeys.log_detail_tabs_bolus_delayedBy, args: {
                    "delay": '${bolus.delay} ${translate(LocalizationKeys.general_suffixes_mins)}'
                  }) : ''}'
              ].join(' + '),
              notes: meal.notes ?? '',
            ))
        .toList();
    final glucoseBoli = boli
        .where((bolus) =>
            Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
                ? bolus.mgPerDlCorrection != null
                : bolus.mmolPerLCorrection != null)
        .toList();
    final otherMealBoli = boli
        .where((bolus) =>
            bolus.meal.targetId != 0 &&
            !meals.any((meal) => meal.id == bolus.meal.targetId))
        .map((bolus) =>
            '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)} ${bolus.delay != null ? translate(LocalizationKeys.log_detail_tabs_bolus_delayedBy, args: {
                    "delay": '${bolus.delay} ${translate(LocalizationKeys.general_suffixes_mins)}'
                  }) : ''}')
        .toList();

    return pw.TableRow(
      decoration: pw.BoxDecoration(
        color: logEntries.indexOf(logEntry) % 2 == 0 ? PdfColors.grey200 : null,
        border: const pw.Border(
          bottom: pw.BorderSide(
            color: baseColor,
            width: .5,
          ),
        ),
      ),
      children: [
        // Time
        pw.Text(DateTimeUtils.displayTime(logEntry.time)),
        // Blood Glucose
        pw.Row(
          children: [
            pw.Text(
              Utils.displayGlucose(
                  mgPerDl: logEntry.mgPerDl, mmolPerL: logEntry.mmolPerL),
              style: glucoseStyle,
            ),
            logEntry.glucoseTrend != null
                ? pw.Transform.rotate(
                    angle: -(logEntry.glucoseTrend! * pi / 180),
                    child: pw.Icon(
                      const pw.IconData(0xe5d8),
                      color: glucoseStyle.color,
                      size: 10.0,
                    ),
                  )
                : pw.Container(),
          ],
        ),
        // Bolus
        pw.Text(
            [for (var bolus in glucoseBoli) '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)}'].join(' + ')),
        // Notes
        pw.Text(logEntry.notes ?? '', style: small),
        // Meals
        pw.Text([for (var meal in meals) meal.text].join('\n'), style: small),
        // Portion Size
        pw.Text([for (var meal in meals) meal.portion].join('\n'),
            style: small),
        // Carbohydrates
        pw.Text([for (var meal in meals) meal.carbs].join('\n'), style: small),
        // Meal Bolus
        pw.Text(
            [
              for (var meal in meals) meal.boli,
              for (var bolus in otherMealBoli) bolus,
            ].join('\n'),
            style: small),
        // Meal Notes
        pw.Text([for (var meal in meals) meal.notes].join('\n'), style: small),
      ],
    );
  }).toList();

  return pw.Table(
    border: null,
    tableWidth: pw.TableWidth.max,
    children: [
          pw.TableRow(
              decoration: const pw.BoxDecoration(
                color: baseColor,
              ),
              children: tableHeaders
                  .map((header) => pw.Text(
                        header,
                        style: pw.TextStyle(
                          color: PdfColors.white,
                          fontWeight: pw.FontWeight.bold,
                        ),
                      ))
                  .toList()),
        ] +
        data,
  );
}

Future<Uint8List> generateLogReport(
    {required DateTime startDate,
    DateTime? endDate,
    bool showChart = true,
    bool showTable = true,
    bool showMeals = true,
    bool showBasal = true,
    bool showBolus = true}) async {
  final List<GlucoseTarget> targets = GlucoseTarget.getAll();

  final document = pw.Document();

  final pageFormat = PdfPageFormat.a4.landscape.applyMargin(
      left: 2.0 * PdfPageFormat.cm,
      top: 2.0 * PdfPageFormat.cm,
      right: 2.0 * PdfPageFormat.cm,
      bottom: 2.0 * PdfPageFormat.cm);

  final theme = pw.ThemeData.withFont(
    base: await PdfGoogleFonts.robotoRegular(),
    bold: await PdfGoogleFonts.robotoBold(),
    icons: await PdfGoogleFonts.materialIcons(),
  ).copyWith(
    defaultTextStyle: const pw.TextStyle(fontSize: 9.0),
  );

  for (DateTime date = startDate;
      endDate != null && !endDate.isBefore(startDate) && !date.isAfter(endDate);
      date = date.add(const Duration(days: 1))) {
    List<LogEntry> logEntries = LogEntry.getAllForDate(date);
    List<LogEvent> logEvents = LogEvent.getAllForDate(date);

    pw.Widget? chart = showChart
        ? await generateChartAsImage(date, logEntries, logEvents, targets,
            showMeals, showBasal, showBolus)
        : pw.Container();
    pw.Widget? table = showTable ? generateTable(logEntries) : pw.Container();

    document.addPage(
      pw.Page(
        pageFormat: pageFormat,
        theme: theme,
        build: (context) {
          return pw.Column(
            children: [
              pw.Text(
                translate(LocalizationKeys.reports_export_title).toUpperCase(),
                style: pw.TextStyle(
                  fontWeight: pw.FontWeight.bold,
                  fontSize: 12,
                  letterSpacing: 2.0,
                ),
              ),
              pw.Text(
                'for ${DateTimeUtils.displayDate(date)}'.toUpperCase(),
                style: const pw.TextStyle(
                  letterSpacing: 2.0,
                ),
              ),
              chart,
              pw.SizedBox(height: 10),
              table,
            ],
          );
        },
      ),
    );
  }

  return await document.save();
}
