using Billing.Models;
using Billing.Themes;
using Billing.UI;
using Microcharts;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
using Resource = Billing.Languages.Resource;

namespace Billing.Views
{
    public enum DateType : int
    {
        Custom = 0,
        Monthly,
        Today,
        PastMonth,
        PastQuarter,
        PastSixMonths,
        PastYear,
        Total
    }

    public partial class RankPage : BillingPage
    {
        private static RankPage instance;
        public static RankPage Instance => instance;

        private static readonly DateTime today = DateTime.Today;

        private static readonly BindableProperty SegmentTypeProperty = Helper.Create<int, RankPage>(nameof(SegmentType), defaultValue: 0, propertyChanged: OnSegmentTypeChanged);
        private static readonly BindableProperty SegmentDateProperty = Helper.Create<int, RankPage>(nameof(SegmentDate), defaultValue: 1, propertyChanged: OnSegmentDateChanged);
        private static readonly BindableProperty StartDateProperty = Helper.Create<DateTime, RankPage>(nameof(StartDate),
            defaultValue: today.AddDays(1 - today.Day),
            propertyChanged: OnDateChanged);
        private static readonly BindableProperty EndDateProperty = Helper.Create<DateTime, RankPage>(nameof(EndDate),
            defaultValue: today.AddDays(DateTime.DaysInMonth(today.Year, today.Month) - today.Day).LastMoment(),
            propertyChanged: OnDateChanged);
        private static readonly BindableProperty StartPickerDateProperty = Helper.Create<DateTime, RankPage>(nameof(StartPickerDate),
            defaultValue: today.AddDays(1 - today.Day),
            propertyChanged: OnPickerStartDateChanged);
        private static readonly BindableProperty EndPickerDateProperty = Helper.Create<DateTime, RankPage>(nameof(EndPickerDate),
            defaultValue: today.AddDays(DateTime.DaysInMonth(today.Year, today.Month) - today.Day),
            propertyChanged: OnPickerEndDateChanged);
        private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart));
        private static readonly BindableProperty CategoryChartProperty = Helper.Create<Chart, RankPage>(nameof(CategoryChart));
        private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills));
        private static readonly BindableProperty NoResultChartProperty = Helper.Create<bool, RankPage>(nameof(NoResultChart));
        private static readonly BindableProperty NoResultCategoryChartProperty = Helper.Create<bool, RankPage>(nameof(NoResultCategoryChart));
        private static readonly BindableProperty NoResultTopBillsProperty = Helper.Create<bool, RankPage>(nameof(NoResultTopBills));
        private static readonly BindableProperty IncomeProperty = Helper.Create<decimal, RankPage>(nameof(Income));
        private static readonly BindableProperty SpendingProperty = Helper.Create<decimal, RankPage>(nameof(Spending));
        private static readonly BindableProperty BalanceProperty = Helper.Create<decimal, RankPage>(nameof(Balance));

        private static void OnSegmentTypeChanged(RankPage page, int old, int @new)
        {
            page.type = @new switch
            {
                1 => CategoryType.Income,
                _ => CategoryType.Spending
            };
            page.LoadData();
        }
        private static void OnSegmentDateChanged(RankPage page, int old, int @new)
        {
            page.OnDateTypeCommand((DateType)@new);
        }
        private static void OnDateChanged(RankPage page, DateTime old = default, DateTime @new = default)
        {
            page.isLocked = true;
            page.StartPickerDate = page.StartDate.Date;
            page.EndPickerDate = page.EndDate.Date;
            page.isLocked = false;
            if (!page.isFreezed)
            {
                var format = Resource.TitleDateFormat;
                page.Title = page.StartDate.ToString(format) + " ~ " + page.EndDate.ToString(format);
                page.LoadData();
            }
        }
        private static void OnPickerStartDateChanged(RankPage page, DateTime _, DateTime @new)
        {
            if (!page.isLocked)
            {
                page.SegmentDate = 0;
                page.StartDate = @new.Date;
            }
        }
        private static void OnPickerEndDateChanged(RankPage page, DateTime _, DateTime @new)
        {
            if (!page.isLocked)
            {
                page.SegmentDate = 0;
                page.EndDate = @new.Date.LastMoment();
            }
        }

        public int SegmentType
        {
            get => (int)GetValue(SegmentTypeProperty);
            set => SetValue(SegmentTypeProperty, value);
        }
        public int SegmentDate
        {
            get => (int)GetValue(SegmentDateProperty);
            set => SetValue(SegmentDateProperty, value);
        }
        public DateTime StartDate
        {
            get => (DateTime)GetValue(StartDateProperty);
            set => SetValue(StartDateProperty, value);
        }
        public DateTime EndDate
        {
            get => (DateTime)GetValue(EndDateProperty);
            set => SetValue(EndDateProperty, value);
        }
        public DateTime StartPickerDate
        {
            get => (DateTime)GetValue(StartPickerDateProperty);
            set => SetValue(StartPickerDateProperty, value);
        }
        public DateTime EndPickerDate
        {
            get => (DateTime)GetValue(EndPickerDateProperty);
            set => SetValue(EndPickerDateProperty, value);
        }
        public Chart Chart
        {
            get => (Chart)GetValue(ChartProperty);
            set => SetValue(ChartProperty, value);
        }
        public Chart CategoryChart
        {
            get => (Chart)GetValue(CategoryChartProperty);
            set => SetValue(CategoryChartProperty, value);
        }
        public IList<UIBill> TopBills
        {
            get => (IList<UIBill>)GetValue(TopBillsProperty);
            set => SetValue(TopBillsProperty, value);
        }
        public bool NoResultChart
        {
            get => (bool)GetValue(NoResultChartProperty);
            set => SetValue(NoResultChartProperty, value);
        }
        public bool NoResultCategoryChart
        {
            get => (bool)GetValue(NoResultCategoryChartProperty);
            set => SetValue(NoResultCategoryChartProperty, value);
        }
        public bool NoResultTopBills
        {
            get => (bool)GetValue(NoResultTopBillsProperty);
            set => SetValue(NoResultTopBillsProperty, value);
        }
        public decimal Income => (decimal)GetValue(IncomeProperty);
        public decimal Spending => (decimal)GetValue(SpendingProperty);
        public decimal Balance => (decimal)GetValue(BalanceProperty);

        public List<string> DateTypes { get; }

        public Command LeftCommand { get; }
        public Command RightCommand { get; }
        public Command FilterCommand { get; }
        public Command EditBilling { get; }

        private IEnumerable<Bill> bills;
        private CategoryType type = CategoryType.Spending;
        private bool isFilterToggled;
        private bool isFreezed;
        private bool isLocked;
        private bool needRefresh = true;

        private const int FILTER_HEIGHT = 100;
        private readonly SKTypeface font;

        public RankPage()
        {
            instance = this;

            LeftCommand = new Command(OnLeftCommand);
            RightCommand = new Command(OnRightCommand);
            FilterCommand = new Command(OnFilterCommand);
            EditBilling = new Command(OnEditBilling);

            var style = SKFontManager.Default.GetFontStyles("PingFang SC");
            if (style != null)
            {
                font = style.CreateTypeface(SKFontStyle.Normal);
            }

            DateTypes = new List<string>
            {
                Resource.Custom,
                Resource.Monthly,
                Resource.Today,
                Resource.PastMonth,
                Resource.PastQuarter,
                Resource.PastSixMonths,
                Resource.PastYear,
                Resource.Total
            };

            InitializeComponent();

            gridFilter.TranslationY = -FILTER_HEIGHT;
            panelFilter.TranslationY = -FILTER_HEIGHT;
        }

        public void SetNeedRefresh()
        {
            needRefresh = true;
        }

        protected override void OnAppearing()
        {
            if (needRefresh)
            {
                needRefresh = false;
                OnDateChanged(this);
            }
        }

        private void OnDateTypeCommand(DateType index)
        {
            if (index < DateType.Monthly || index > DateType.Total)
            {
                return;
            }
            if (scroller.ScrollY > 0)
            {
                scroller.ScrollToAsync(0, 0, true);
            }
            isFreezed = true;
            var today = DateTime.Today;
            switch (index)
            {
                case DateType.Monthly:
                    StartDate = today.AddDays(1 - today.Day);
                    EndDate = today.AddDays(DateTime.DaysInMonth(today.Year, today.Month) - today.Day).LastMoment();
                    break;
                case DateType.Today:
                    StartDate = today;
                    EndDate = today.LastMoment();
                    break;
                case DateType.PastMonth:
                    StartDate = today.AddMonths(-1).AddDays(1);
                    EndDate = today.LastMoment();
                    break;
                case DateType.PastQuarter:
                    StartDate = today.AddMonths(-3).AddDays(1);
                    EndDate = today.LastMoment();
                    break;
                case DateType.PastSixMonths:
                    StartDate = today.AddMonths(-6).AddDays(1);
                    EndDate = today.LastMoment();
                    break;
                case DateType.PastYear:
                    StartDate = today.AddYears(-1).AddDays(1);
                    EndDate = today.LastMoment();
                    break;
                case DateType.Total:
                    //StartDate = App.Bills.Min(b => b.CreateTime).Date;
                    //EndDate = App.Bills.Max(b => b.CreateTime).Date.LastMoment();
                    DateTime min = DateTime.MaxValue;
                    DateTime max = DateTime.MinValue;
                    App.Bills.ForEach(b =>
                    {
                        if (b.CreateTime < min)
                        {
                            min = b.CreateTime;
                        }
                        if (b.CreateTime > max)
                        {
                            max = b.CreateTime;
                        }
                    });
                    if (min == DateTime.MaxValue && max == DateTime.MinValue)
                    {
                        return;
                    }
                    StartDate = min.Date;
                    EndDate = max.Date.LastMoment();
                    break;
            }
            isFreezed = false;
            OnDateChanged(this);
        }

        private bool IsPreset(DateTime start, DateTime end)
        {
            return start.Month == end.Month &&
                start.Day == 1 &&
                end.Day == DateTime.DaysInMonth(end.Year, end.Month);
        }

        private void OnLeftCommand()
        {
            var type = (DateType)SegmentDate;
            if (type < DateType.Monthly || type >= DateType.Total)
            {
                return;
            }
            if (scroller.ScrollY > 0)
            {
                scroller.ScrollToAsync(0, 0, true);
            }
            isFreezed = true;
            var start = StartDate;
            var end = EndDate;
            if (type == DateType.Monthly || IsPreset(start, end))
            {
                start = start.AddMonths(-1);
                end = start.AddDays(DateTime.DaysInMonth(start.Year, start.Month) - 1).LastMoment();
            }
            else if (type == DateType.PastMonth)
            {
                start = start.AddMonths(-1);
                end = end.AddMonths(-1);
            }
            else if (type == DateType.PastQuarter)
            {
                start = start.AddMonths(-3);
                end = end.AddMonths(-3);
            }
            else if (type == DateType.PastSixMonths)
            {
                start = start.AddMonths(-6);
                end = end.AddMonths(-6);
            }
            else if (type == DateType.PastYear)
            {
                start = start.AddYears(-1);
                end = end.AddYears(-1);
            }
            else
            {
                var days = (end.Date - start.Date).TotalDays + 1;
                start = start.AddDays(-days);
                end = end.AddDays(-days);
            }
            if (start.Year < 1900)
            {
                isFreezed = false;
                return;
            }
            StartDate = start;
            EndDate = end;
            isFreezed = false;
            OnDateChanged(this);
        }

        private void OnRightCommand()
        {
            var type = (DateType)SegmentDate;
            if (type < DateType.Monthly || type >= DateType.Total)
            {
                return;
            }
            if (scroller.ScrollY > 0)
            {
                scroller.ScrollToAsync(0, 0, true);
            }
            isFreezed = true;
            var start = StartDate;
            var end = EndDate;
            if (type == DateType.Monthly || IsPreset(start, end))
            {
                start = start.AddMonths(1);
                end = start.AddDays(DateTime.DaysInMonth(start.Year, start.Month) - 1).LastMoment();
            }
            else if (type == DateType.PastMonth)
            {
                start = start.AddMonths(1);
                end = end.AddMonths(1);
            }
            else if (type == DateType.PastQuarter)
            {
                start = start.AddMonths(3);
                end = end.AddMonths(3);
            }
            else if (type == DateType.PastSixMonths)
            {
                start = start.AddMonths(6);
                end = end.AddMonths(6);
            }
            else if (type == DateType.PastYear)
            {
                start = start.AddYears(1);
                end = end.AddYears(1);
            }
            else
            {
                var days = (end.Date - start.Date).TotalDays + 1;
                start = start.AddDays(days);
                end = end.AddDays(days);
            }
            if (end.Year > DateTime.Today.Year + 100)
            {
                isFreezed = false;
                return;
            }
            StartDate = start;
            EndDate = end;
            isFreezed = false;
            OnDateChanged(this);
        }

        private async void OnFilterCommand(object o)
        {
            if (o is bool flag)
            {
                isFilterToggled = flag;
            }
            else
            {
                isFilterToggled = !isFilterToggled;
            }
            ViewExtensions.CancelAnimations(gridFilter);
            ViewExtensions.CancelAnimations(panelFilter);
            if (isFilterToggled)
            {
                await scroller.ScrollToAsync(scroller.ScrollX, scroller.ScrollY, false);
                await Task.WhenAll(
                    gridFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
                    gridFilter.FadeTo(1, easing: Easing.CubicOut),
                    panelFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
                    panelFilter.FadeTo(1, easing: Easing.CubicOut));
            }
            else
            {
                await Task.WhenAll(
                    gridFilter.TranslateTo(0, -FILTER_HEIGHT, easing: Easing.CubicIn),
                    gridFilter.FadeTo(0, easing: Easing.CubicIn),
                    panelFilter.TranslateTo(0, -FILTER_HEIGHT, easing: Easing.CubicIn),
                    panelFilter.FadeTo(0, easing: Easing.CubicIn));
            }
        }

        private async void OnEditBilling(object o)
        {
            if (Tap.IsBusy)
            {
                return;
            }
            using (Tap.Start())
            {
                if (o is UIBill bill)
                {
                    var page = new AddBillPage(bill.Bill);
                    page.BillChecked += OnBillChecked;
                    await Navigation.PushAsync(page);
                }
            }
        }

        private async void RefreshBalance(DateTime start, DateTime end)
        {
            var bills = await Task.Run(() => App.Bills.Where(b => b.CreateTime >= start && b.CreateTime <= end));
            var income = bills.Where(b => b.Amount > 0).Sum(b => b.Amount);
            var spending = -bills.Where(b => b.Amount < 0).Sum(b => b.Amount);
            SetValue(IncomeProperty, income);
            SetValue(SpendingProperty, spending);
            SetValue(BalanceProperty, income - spending);
        }

        private async void OnBillChecked(object sender, Bill e)
        {
            await Task.Run(App.WriteBills);
            LoadData();
        }

        private async void LoadData()
        {
            var start = StartDate;
            var end = EndDate;
            var spending = type == CategoryType.Spending;
            bills = await Task.Run(() => App.Bills.Where(b => (b.Amount > 0 ^ spending) && b.CreateTime >= start && b.CreateTime <= end));

            var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor();
            var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor();

            _ = Task.Run(() => LoadReportChart(primaryColor, textColor, start, end));
            _ = Task.Run(() => LoadCategoryChart(primaryColor, textColor));
            _ = Task.Run(LoadTopBills);

            RefreshBalance(start, end);
        }

        private void LoadReportChart(SKColor primaryColor, SKColor textColor, DateTime start, DateTime end)
        {
            var entries = new List<ChartEntry>();
            for (var day = start; day <= end; day = day.AddDays(1))
            {
                var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day));
                decimal amount = Math.Abs(daybills.Sum(b => b.Amount));
                if (amount > 0)
                {
                    entries.Add(new((float)amount)
                    {
                        Label = day.ToString("MM-dd"),
                        ValueLabel = amount.ToString("#,##0.##"),
                        Color = primaryColor,
                        TextColor = textColor,
                        ValueLabelColor = textColor
                    });
                }
            }

            MainThread.BeginInvokeOnMainThread(() =>
            {
                if (entries.Count > 0)
                {
                    NoResultChart = false;
                    Chart = new LineChart
                    {
                        BackgroundColor = SKColors.Transparent,
                        LabelTextSize = 24,
                        Entries = entries
                    };
                }
                else
                {
                    Chart = null;
                    NoResultChart = true;
                }
            });
        }

        private void LoadCategoryChart(SKColor primaryColor, SKColor textColor)
        {
            var entries = new List<ChartEntry>();
            var groups = bills.GroupBy(b => b.CategoryId);
            var dict = new Dictionary<string, decimal>();
            decimal all = 0m;
            foreach (var g in groups)
            {
                var categoryId = g.Key;
                var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
                if (category?.ParentId != null)
                {
                    category = App.Categories.FirstOrDefault(c => c.Id == category.ParentId) ?? category;
                }
                if (category != null)
                {
                    var total = Math.Abs(g.Sum(g => g.Amount));
                    all += total;
                    if (dict.ContainsKey(category.Name))
                    {
                        dict[category.Name] += total;
                    }
                    else
                    {
                        dict.Add(category.Name, total);
                    }
                }
            }
            foreach (var kv in dict)
            {
                entries.Add(new((float)kv.Value)
                {
                    Label = kv.Key,
                    ValueLabel = (kv.Value * 100 / all).ToString("0.#") + "%",
                    Color = primaryColor,
                    TextColor = textColor,
                    ValueLabelColor = textColor
                });
            }

            MainThread.BeginInvokeOnMainThread(() =>
            {
                if (entries.Count > 0)
                {
                    NoResultCategoryChart = false;
                    CategoryChart = new RadarChart
                    {
                        BackgroundColor = SKColors.Transparent,
                        LabelTextSize = 30,
                        Typeface = font,
                        Entries = entries
                    };
                }
                else
                {
                    CategoryChart = null;
                    NoResultCategoryChart = true;
                }
            });
        }

        private void LoadTopBills()
        {
            List<UIBill> topBills = bills.OrderByDescending(b => Math.Abs(b.Amount)).Take(10).Select(b => Helper.WrapBill(b)).ToList();

            MainThread.BeginInvokeOnMainThread(() =>
            {
                if (topBills.Count > 0)
                {
                    NoResultTopBills = false;
                    TopBills = topBills;
                }
                else
                {
                    TopBills = null;
                    NoResultTopBills = true;
                }
            });
        }

        private void Scroller_Scrolled(object sender, ScrolledEventArgs e)
        {
            if (isFilterToggled)
            {
                OnFilterCommand(false);
            }
        }
    }
}