using Blahblah.FlowerApp.Controls;
using Blahblah.FlowerApp.Data;
using Blahblah.FlowerApp.Data.Model;
using Blahblah.FlowerApp.Views.Garden;
using Microsoft.Extensions.Logging;
using System.Net;
using static Blahblah.FlowerApp.Extensions;

namespace Blahblah.FlowerApp;

public partial class HomePage : AppContentPage
{
    static readonly BindableProperty SearchKeyProperty = CreateProperty<string, HomePage>(nameof(SearchKey), propertyChanged: OnSearchKeyPropertyChanged);
    static readonly BindableProperty FlowersProperty = CreateProperty<FlowerClientItem[], HomePage>(nameof(Flowers));
    static readonly BindableProperty IsRefreshingProperty = CreateProperty<bool, HomePage>(nameof(IsRefreshing));
    static readonly BindableProperty CurrentCountProperty = CreateProperty<string?, HomePage>(nameof(CurrentCount));

    static void OnSearchKeyPropertyChanged(BindableObject bindable, object old, object @new)
    {
        if (bindable is HomePage home && @new is string)
        {
            if (home.IsRefreshing)
            {
                home.changed = true;
            }
            else
            {
                home.IsRefreshing = true;
            }
        }
    }

    public string SearchKey
    {
        get => GetValue<string>(SearchKeyProperty);
        set => SetValue(SearchKeyProperty, value);
    }
    public FlowerClientItem[] Flowers
    {
        get => GetValue<FlowerClientItem[]>(FlowersProperty);
        set => SetValue(FlowersProperty, value);
    }
    public bool IsRefreshing
    {
        get => GetValue<bool>(IsRefreshingProperty);
        set => SetValue(IsRefreshingProperty, value);
    }
    public string? CurrentCount
    {
        get => GetValue<string?>(CurrentCountProperty);
        set => SetValue(CurrentCountProperty, value);
    }

    bool logined = false;
    bool loaded = false;
    bool? setup;
    double pageWidth;
    bool changed = false;

    const int margin = 12;
    const int cols = 2;
    double[] ys = null!;
    int yIndex;
    int itemWidth;
    int emptyHeight;

    public HomePage(FlowerDatabase database, ILogger<HomePage> logger) : base(database, logger)
    {
        InitializeComponent();

        Task.Run(async () =>
        {
            try
            {
                await Database.Setup();
            }
            catch (Exception ex)
            {
                this.LogError(ex, $"error occurs when setting up database, {ex.Message}");
            }
            finally
            {
                setup = true;
            }
        });
    }

    protected override void OnSizeAllocated(double width, double height)
    {
        base.OnSizeAllocated(width, height);

        if (!logined)
        {
            logined = true;
            IsRefreshing = true;

            Task.Run(async () =>
            {
                while (setup == null)
                {
                    await Task.Delay(100);
                }

                await DoValidationAsync();

                if (!loaded)
                {
                    loaded = true;
                    await DoRefreshAsync();
                }
            });
        }

        pageWidth = width - margin * 2;
        if (loaded && Flowers?.Length > 0)
        {
            DoInitSize();
            foreach (var item in Flowers)
            {
                DoResizeItem(item);
            }
        }
    }

    private async Task<bool> DoValidationAsync()
    {
        bool invalid = true;
        var oAuth = Constants.Authorization;
        if (!string.IsNullOrEmpty(oAuth))
        {
            try
            {
                var user = await FetchAsync<UserItem>("api/user/profile");
                if (user != null)
                {
                    invalid = false;
                    AppResources.SetUser(user);
                }
            }
            catch (Exception ex)
            {
                this.LogError(ex, $"token is invalid, token: {oAuth}, {ex.Message}");
            }
        }
        if (invalid)
        {
            var source = new TaskCompletionSource<bool>();
            MainThread.BeginInvokeOnMainThread(() =>
            {
                var login = new LoginPage(Database, Logger);
                var sheet = this.ShowBottomSheet(login);
                login.AfterLogined += (sender, user) =>
                {
                    sheet.CloseBottomSheet();
                    source.TrySetResult(true);
                };
            });
            return await source.Task;
        }
        return true;
    }

    private void DoInitSize()
    {
        ys = new double[cols];
        yIndex = 0;
        itemWidth = (int)(pageWidth / cols) - margin * (cols - 1) / 2;
        emptyHeight = (int)(itemWidth * AppResources.EmptySize.Height / AppResources.EmptySize.Width);
    }

    private void DoResizeItem(FlowerClientItem item)
    {
        int height;
        if (item.Width > 0 && item.Height > 0)
        {
            height = itemWidth * item.Height.Value / item.Width.Value;
        }
        else
        {
            height = emptyHeight;
        }
        height += 46;
        double yMin = double.MaxValue;
        for (var i = 0; i < cols; i++)
        {
            if (ys[i] < yMin)
            {
                yMin = ys[i];
                yIndex = i;
            }
        }
        ys[yIndex] += height + margin;
        item.Bounds = new Rect(
            yIndex,
            yMin,
            itemWidth,
            height);
    }

    private async Task DoRefreshAsync()
    {
        try
        {
            var url = "api/flower/latest?photo=true";
            var key = SearchKey;
            if (!string.IsNullOrWhiteSpace(key))
            {
                url += "&key=" + WebUtility.UrlEncode(key);
            }
            var result = await FetchAsync<FlowerResult>(url);
            if (result?.Count > 0)
            {
                CurrentCount = L("currentCount", "There are currently {count} plants").Replace("{count}", result.Count.ToString());

                await Database.UpdateFlowers(result.Flowers);

                DoInitSize();
                var daystring = L("daysPlanted", "{count} days planted");
                var flowers = result.Flowers.Select(f =>
                {
                    var item = new FlowerClientItem(f);
                    var days = (DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeMilliseconds(f.DateBuyUnixTime)).TotalDays;
                    item.Days = daystring.Replace("{count}", ((int)days).ToString());
                    if (f.Photos?.Length > 0 && f.Photos[0] is PhotoItem cover)
                    {
                        item.Cover = new UriImageSource { Uri = new Uri($"{Constants.BaseUrl}/{cover.Url}") };
                    }
                    else
                    {
                        item.Cover = "empty_flower.jpg";
                    }
                    DoResizeItem(item);
                    return item;
                });

                Flowers = flowers.ToArray();
            }
            else
            {
                CurrentCount = null;
                Flowers = Array.Empty<FlowerClientItem>();
            }
        }
        catch (Exception ex)
        {
            await this.AlertError(L("failedGetFlowers", "Failed to get flowers, please try again."));
            this.LogError(ex, $"error occurs in HomePage, {ex.Message}");
        }
        finally
        {
            IsRefreshing = false;

            if (changed)
            {
                changed = false;
                await Task.Delay(100);
                IsRefreshing = true;
            }
        }
    }

    private void RefreshView_Refreshing(object sender, EventArgs e)
    {
        if (loaded)
        {
            Task.Run(DoRefreshAsync);
        }
    }

    private async void AddFlower_Clicked(object sender, EventArgs e)
    {
        //await Shell.Current.GoToAsync("AddFlower");
        var addPage = new AddFlowerPage(Database, Logger);
        await Navigation.PushAsync(addPage);
    }
}

public record FlowerResult
{
    public FlowerItem[] Flowers { get; init; } = null!;

    public int Count { get; init; }
}