import 'package:currency_picker/currency_picker.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:prasule/api/category.dart';
import 'package:prasule/api/wallet_entry.dart';
import 'package:prasule/util/get_last_date.dart';
import 'package:prasule/util/text_color.dart';

/// Monthly/Yearly expense/income [LineChart]
class ExpensesLineChart extends StatelessWidget {
  /// Monthly/Yearly expense/income [LineChart]
  const ExpensesLineChart({
    required this.date,
    required this.locale,
    required this.expenseData,
    required this.incomeData,
    required this.currency,
    super.key,
    this.yearly = false,
  });

  /// If the graph will be shown yearly
  final bool yearly;

  /// Selected date
  ///
  /// Used to get either month or year
  final DateTime date;

  /// Current locale
  ///
  /// Used mainly for formatting
  final String locale;

  /// The expense data used for the graph
  final List<double> expenseData;

  /// Wallet currency
  ///
  /// Used to show currency symbol
  final Currency currency;

  /// Expense data, but sorted
  List<double> get expenseDataSorted =>
      List<double>.from(expenseData)..sort((a, b) => a.compareTo(b));

  /// Income data used for the graph
  final List<double> incomeData;

  /// Income data, but sorted
  List<double> get incomeDataSorted =>
      List<double>.from(incomeData)..sort((a, b) => a.compareTo(b));

  /// Calculates maxY for the graph
  double get maxY {
    if (incomeData.isEmpty) return expenseDataSorted.last;
    if (expenseData.isEmpty) return incomeDataSorted.last;
    if (expenseDataSorted.last > incomeDataSorted.last) {
      return expenseDataSorted.last;
    } else {
      return incomeDataSorted.last;
    }
  }

  @override
  Widget build(BuildContext context) {
    return LineChart(
      LineChartData(
        lineTouchData: LineTouchData(
          touchTooltipData: LineTouchTooltipData(
            getTooltipColor: (group) =>
                Theme.of(context).colorScheme.secondaryContainer,
            getTooltipItems: (spots) => List<LineTooltipItem>.generate(
              spots.length,
              (index) => LineTooltipItem(
                // Changes what's rendered on the tooltip
                // when clicked in the chart
                (spots[index].barIndex == 0 &&
                        incomeData.isNotEmpty) // income chart
                    ? (yearly
                        ? AppLocalizations.of(context).incomeForMonth(
                            DateFormat.MMMM(locale).format(
                              DateTime(
                                date.year,
                                spots[index].x.toInt() + 1,
                              ),
                            ),
                            NumberFormat.compactCurrency(
                              locale: locale,
                              symbol: currency.symbol,
                              name: currency.name,
                            ).format(spots[index].y),
                          )
                        : AppLocalizations.of(context).incomeForDay(
                            NumberFormat.compactCurrency(
                              locale: locale,
                              symbol: currency.symbol,
                              name: currency.name,
                            ).format(spots[index].y),
                          ))
                    : (yearly // expense chart
                        ? AppLocalizations.of(context).expensesForMonth(
                            DateFormat.MMMM(locale).format(
                              DateTime(
                                date.year,
                                spots[index].x.toInt() + 1,
                              ),
                            ),
                            NumberFormat.compactCurrency(
                              locale: locale,
                              symbol: currency.symbol,
                              name: currency.name,
                            ).format(spots[index].y),
                          )
                        : AppLocalizations.of(context).expensesForDay(
                            NumberFormat.compactCurrency(
                              locale: locale,
                              symbol: currency.symbol,
                              name: currency.name,
                            ).format(spots[index].y),
                          )),
                TextStyle(color: spots[index].bar.color),
                children: [
                  if (!yearly)
                    TextSpan(
                      text:
                          "\n${DateFormat.yMMMMd(locale).format(DateTime(date.year, date.month, spots[index].spotIndex + 1))}",
                    ),
                ],
              ),
            ),
          ),
        ),
        maxY: maxY,
        maxX: yearly
            ? 11
            : date.lastDay.toDouble() -
                1, // remove 1 because we are indexing from 0
        minY: 0,
        minX: 0,
        lineBarsData: [
          if (incomeData.isNotEmpty)
            LineChartBarData(
              isCurved: true,
              barWidth: 8,
              isStrokeCapRound: true,
              dotData: const FlDotData(show: false),
              belowBarData: BarAreaData(),
              color: ((MediaQuery.of(context).platformBrightness ==
                          Brightness.dark)
                      ? Colors.green.shade300
                      : Colors.green)
                  .harmonizeWith(Theme.of(context).colorScheme.primary),
              spots: List.generate(
                yearly ? 12 : date.lastDay,
                (index) => FlSpot(index.toDouble(), incomeData[index]),
              ),
            ),
          if (expenseData.isNotEmpty)
            LineChartBarData(
              isCurved: true,
              barWidth: 8,
              isStrokeCapRound: true,
              dotData: const FlDotData(show: false),
              belowBarData: BarAreaData(),
              color: ((MediaQuery.of(context).platformBrightness ==
                          Brightness.dark)
                      ? Colors.red.shade300
                      : Colors.red)
                  .harmonizeWith(Theme.of(context).colorScheme.primary),
              spots: List.generate(
                yearly
                    ? 12
                    : date.lastDay, // no -1 because it's the length, not index
                (index) => FlSpot(index.toDouble(), expenseData[index]),
              ),
            ),
        ], // actual data
        titlesData: FlTitlesData(
          rightTitles: const AxisTitles(),
          topTitles: const AxisTitles(),
          leftTitles: AxisTitles(
            sideTitles: SideTitles(
              reservedSize: ((expenseDataSorted.isNotEmpty &&
                          NumberFormat.compact(locale: locale)
                                  .format(expenseDataSorted.last)
                                  .length >=
                              5) ||
                      (incomeDataSorted.isNotEmpty &&
                          NumberFormat.compact(locale: locale)
                                  .format(incomeDataSorted.last)
                                  .length >=
                              5))
                  ? 50
                  : 25,
              showTitles: true,
            ),
          ),
          bottomTitles: AxisTitles(
            sideTitles: SideTitles(
              reservedSize: 30,
              showTitles: true,
              getTitlesWidget: (value, meta) {
                String text;
                if (yearly) {
                  text = DateFormat.MMM(locale).format(
                    DateTime(date.year, value.toInt() + 1),
                  );
                } else {
                  text = (value.toInt() + 1).toString();
                }
                return SideTitleWidget(
                  axisSide: meta.axisSide,
                  child: Text(text),
                );
              },
            ),
          ),
        ), // axis descriptions
      ),
    );
  }
}

/// Renders expenses/income as a [BarChart]
class ExpensesBarChart extends StatelessWidget {
  /// Renders expenses/income as a [BarChart]
  const ExpensesBarChart({
    required this.yearly,
    required this.date,
    required this.locale,
    required this.expenseData,
    required this.incomeData,
    required this.currency,
    super.key,
  });

  /// If the graph will be shown yearly
  final bool yearly;

  /// Selected date
  ///
  /// Used to get either month or year
  final DateTime date;

  /// Current locale
  ///
  /// Used mainly for formatting
  final String locale;

  /// The expense data used for the graph
  final List<double> expenseData;

  /// Wallet currency
  ///
  /// Used to show currency symbol
  final Currency currency;

  /// Expense data, but sorted
  List<double> get expenseDataSorted =>
      List<double>.from(expenseData)..sort((a, b) => a.compareTo(b));

  /// Income data used for the graph
  final List<double> incomeData;

  /// Income data, but sorted
  List<double> get incomeDataSorted =>
      List<double>.from(incomeData)..sort((a, b) => a.compareTo(b));

  /// Calculates maxY for the graph
  double get maxY {
    if (incomeData.isEmpty) return expenseDataSorted.last;
    if (expenseData.isEmpty) return incomeDataSorted.last;
    if (expenseDataSorted.last > incomeDataSorted.last) {
      return expenseDataSorted.last;
    } else {
      return incomeDataSorted.last;
    }
  }

  @override
  Widget build(BuildContext context) => BarChart(
        BarChartData(
          barTouchData: BarTouchData(
            enabled: true,
            touchTooltipData: BarTouchTooltipData(
              getTooltipItem: (group, groupIndex, rod, rodIndex) =>
                  yearly // create custom tooltips for graph bars
                      ? BarTooltipItem(
                          (rodIndex == 1 || incomeData.isEmpty) // expense
                              ? AppLocalizations.of(context).expensesForMonth(
                                  DateFormat.MMMM(locale).format(
                                    DateTime(date.year, groupIndex + 1),
                                  ),
                                  NumberFormat.compactCurrency(
                                    locale: locale,
                                    symbol: currency.symbol,
                                    name: currency.name,
                                  ).format(rod.toY),
                                )
                              : AppLocalizations.of(context).incomeForMonth(
                                  // income
                                  DateFormat.MMMM(locale).format(
                                    DateTime(date.year, groupIndex + 1),
                                  ),
                                  NumberFormat.compactCurrency(
                                    locale: locale,
                                    symbol: currency.symbol,
                                    name: currency.name,
                                  ).format(rod.toY),
                                ),
                          TextStyle(color: rod.color),
                        )
                      : BarTooltipItem(
                          (rodIndex == 1)
                              ? AppLocalizations.of(context).expensesForDay(
                                  NumberFormat.compactCurrency(
                                    locale: locale,
                                    symbol: currency.symbol,
                                    name: currency.name,
                                  ).format(rod.toY),
                                )
                              : AppLocalizations.of(context).incomeForDay(
                                  NumberFormat.compactCurrency(
                                    locale: locale,
                                    symbol: currency.symbol,
                                    name: currency.name,
                                  ).format(rod.toY),
                                ),
                          TextStyle(color: rod.color),
                        ),
            ),
          ),
          titlesData: FlTitlesData(
            rightTitles: const AxisTitles(),
            topTitles: const AxisTitles(),
            bottomTitles: AxisTitles(
              sideTitles: SideTitles(
                showTitles: true,
                reservedSize: 30,
                getTitlesWidget: (value, meta) {
                  String text;
                  if (yearly) {
                    text = DateFormat.MMM(locale).format(
                      DateTime(date.year, value.toInt() + 1),
                    );
                  } else {
                    text = (value.toInt() + 1).toString();
                  }
                  return SideTitleWidget(
                    axisSide: meta.axisSide,
                    child: Text(text),
                  );
                },
              ),
            ),
          ), // axis descriptions,
          minY: 0,
          maxY: maxY,
          barGroups: List<BarChartGroupData>.generate(
            yearly ? 12 : date.lastDay - 1,
            (index) => BarChartGroupData(
              x: index,
              barRods: [
                if (incomeData.isNotEmpty)
                  BarChartRodData(
                    toY: incomeData[index],
                    color: Colors.green
                        .harmonizeWith(Theme.of(context).colorScheme.primary),
                  ),
                if (expenseData.isNotEmpty)
                  BarChartRodData(
                    toY: expenseData[index],
                    color: Colors.red
                        .harmonizeWith(Theme.of(context).colorScheme.primary),
                  ),
              ],
            ),
          ),
        ),
      );
}

/// [PieChart] used to display expenses/income visualized
/// under their respective category
class CategoriesPieChart extends StatefulWidget {
  /// [PieChart] used to display expenses/income visualized
  /// under their respective category
  const CategoriesPieChart({
    required this.entries,
    required this.categories,
    required this.symbol,
    required this.locale,
    super.key,
  });

  /// Entries to be used
  final List<WalletSingleEntry> entries;

  /// Categories to be displayed
  final List<WalletCategory> categories;

  /// Currency symbol displayed on the chart
  final String symbol;

  /// User locale
  final String locale;

  @override
  State<CategoriesPieChart> createState() => _CategoriesPieChartState();
}

class _CategoriesPieChartState extends State<CategoriesPieChart> {
  int touchedIndex = -1;

  @override
  Widget build(BuildContext context) => Column(
        children: [
          SizedBox(
            width: MediaQuery.of(context).size.width,
            child: Wrap(
              alignment: WrapAlignment.center,
              spacing: 4,
              children: List<Widget>.generate(
                widget.categories.length,
                (index) => Padding(
                  padding: const EdgeInsets.all(8),
                  child: Indicator(
                    size: (touchedIndex != -1 &&
                            touchedIndex ==
                                widget.categories
                                    .where(
                                      (element) => widget.entries
                                          .where(
                                            (w) =>
                                                w.category.id ==
                                                element
                                                    .id, // TODO: more elegant fix
                                          )
                                          .isNotEmpty,
                                    )
                                    .toList()
                                    .indexOf(widget.categories[index]))
                        ? 18
                        : 16,
                    color: widget.categories[index].color,
                    text: widget.categories[index].name,
                    textStyle: TextStyle(
                      fontWeight: (touchedIndex != -1 &&
                              touchedIndex ==
                                  widget.categories
                                      .where(
                                        (element) => widget.entries
                                            .where(
                                              (w) =>
                                                  w.category.id == element.id,
                                            )
                                            .isNotEmpty,
                                      )
                                      .toList()
                                      .indexOf(widget.categories[index]))
                          ? FontWeight.bold
                          : FontWeight.normal,
                    ),
                  ),
                ),
              ),
            ),
          ),
          const SizedBox(
            height: 5,
          ),
          LimitedBox(
            maxHeight: MediaQuery.of(context).size.height * 0.23,
            maxWidth: MediaQuery.of(context).size.width * 0.9,
            child: PieChart(
              PieChartData(
                centerSpaceRadius: double.infinity,
                pieTouchData: PieTouchData(
                  touchCallback: (event, response) {
                    // Set touchedIndex so we can highlight
                    // the corresponding indicator
                    setState(() {
                      if (!event.isInterestedForInteractions ||
                          response == null ||
                          response.touchedSection == null) {
                        touchedIndex = -1;
                        return;
                      }
                      touchedIndex =
                          response.touchedSection!.touchedSectionIndex;
                    });
                  },
                ),
                sections: List<PieChartSectionData>.generate(
                  widget.categories.length,
                  (index) => PieChartSectionData(
                    title: NumberFormat.compactCurrency(
                      symbol: widget.symbol,
                      locale: widget.locale,
                    ).format(
                      widget.entries
                          .where(
                            (element) =>
                                element.category.id ==
                                widget.categories[index].id,
                          )
                          .fold<double>(
                            0,
                            (previousValue, element) =>
                                previousValue + element.data.amount,
                          ),
                    ),
                    titleStyle: TextStyle(
                      color:
                          widget.categories[index].color.calculateTextColor(),
                      fontWeight: FontWeight.bold,
                      backgroundColor: widget.categories[index].color,
                    ),
                    color: widget.categories[index].color,
                    value: widget.entries
                        .where(
                          (element) =>
                              element.category.id ==
                              widget.categories[index].id,
                        )
                        .fold<double>(
                          0,
                          (previousValue, element) =>
                              previousValue + element.data.amount,
                        ),
                  ),
                ),
              ),
            ),
          ),
        ],
      );
}

/// Used to indicate which part of a chart is for what
class Indicator extends StatelessWidget {
  /// Used to indicate which part of a chart is for what
  const Indicator({
    required this.size,
    required this.color,
    required this.text,
    this.textStyle = const TextStyle(),
    super.key,
  });

  /// Size of the indicator circle
  final double size;

  /// Color of the indicator circle
  final Color color;

  /// Text shown next to the indicator circle
  final String text;

  /// Text style of the indicator
  final TextStyle textStyle;

  @override
  Widget build(BuildContext context) => Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: size,
            height: size,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: color,
            ),
          ),
          const SizedBox(
            width: 4,
          ),
          Text(text, style: textStyle),
        ],
      );
}
