using Blahblah.FlowerApp.Controls;
using Blahblah.FlowerApp.Data;
using Blahblah.FlowerApp.Data.Model;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using static Blahblah.FlowerApp.Extensions;

namespace Blahblah.FlowerApp.Views.Garden;

public partial class AddFlowerPage : AppContentPage
{
    static readonly BindableProperty CurrentLocationProperty = CreateProperty<Location?, AddFlowerPage>(nameof(CurrentLocation), propertyChanged: OnCurrentLocationPropertyChanged);
    static readonly BindableProperty CurrentLocationStringProperty = CreateProperty<string?, AddFlowerPage>(nameof(CurrentLocationString), defaultValue: L("locating", "Locating..."));

    static void OnCurrentLocationPropertyChanged(BindableObject bindable, object old, object @new)
    {
        if (bindable is AddFlowerPage page)
        {
            if (@new is Location loc)
            {
                Task.Run(async () =>
                {
                    string? city = null;
                    try
                    {
                        var location = WebUtility.UrlEncode($"{{\"x\":{loc.Longitude},\"y\":{loc.Latitude},\"spatialReference\":{{\"wkid\":4326}}}}");
                        using var client = new HttpClient();
                        var result = await client.GetFromJsonAsync<GeoResult>($"https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode?location={location}&distance=100&f=json");

                        if (result != null)
                        {
                            if (result.Address == null)
                            {
                                page.LogWarning($"failed to query geo location, with message: {result.Error?.Message}");
                            }
                            else
                            {
                                city = result.Address.City;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        city = L("unknown", "Unknown");
                        page.LogError(ex, $"error occurs when quering geo location: {ex.Message}");
                    }
                    page.SetValue(CurrentLocationStringProperty, city ?? L("unknown", "Unknown"));
                });
            }
            else
            {
                page.SetValue(CurrentLocationStringProperty, L("unknown", "Unknown"));
            }
        }
    }

    public Location? CurrentLocation
    {
        get => GetValue<Location?>(CurrentLocationProperty);
        set => SetValue(CurrentLocationProperty, value);
    }
    public string? CurrentLocationString
    {
        get => GetValue<string?>(CurrentLocationStringProperty);
        set => SetValue(CurrentLocationStringProperty, value);
    }

    static readonly BindableProperty CoverProperty = CreateProperty<string?, AddFlowerPage>(nameof(Cover));
    static readonly BindableProperty FlowerNameProperty = CreateProperty<string, AddFlowerPage>(nameof(FlowerName));
    static readonly BindableProperty FlowerLocationProperty = CreateProperty<string?, AddFlowerPage>(nameof(FlowerLocation), defaultValue: L("selectFlowerLocation", "Please select the location"));
    static readonly BindableProperty CategoryProperty = CreateProperty<string, AddFlowerPage>(nameof(Category), defaultValue: L("selectFlowerCategory", "Please select the flower category"));
    static readonly BindableProperty PurchaseDateProperty = CreateProperty<DateTime, AddFlowerPage>(nameof(PurchaseDate));
    static readonly BindableProperty PurchaseTimeProperty = CreateProperty<TimeSpan, AddFlowerPage>(nameof(PurchaseTime));
    static readonly BindableProperty PurchaseFromProperty = CreateProperty<string?, AddFlowerPage>(nameof(PurchaseFrom), defaultValue: L("selectPurchaseFrom", "Please select where are you purchase from"));
    static readonly BindableProperty CostProperty = CreateProperty<string?, AddFlowerPage>(nameof(Cost));
    static readonly BindableProperty MemoProperty = CreateProperty<string?, AddFlowerPage>(nameof(Memo));
    public string? Cover
    {
        get => GetValue<string?>(CoverProperty);
        set => SetValue(CoverProperty, value);
    }
    public string FlowerName
    {
        get => GetValue<string>(FlowerNameProperty);
        set => SetValue(FlowerNameProperty, value);
    }
    public string? FlowerLocation
    {
        get => GetValue<string?>(FlowerLocationProperty);
        set => SetValue(FlowerLocationProperty, value);
    }
    public string Category
    {
        get => GetValue<string>(CategoryProperty);
        set => SetValue(CategoryProperty, value);
    }
    public DateTime PurchaseDate
    {
        get => GetValue<DateTime>(PurchaseDateProperty);
        set => SetValue(PurchaseDateProperty, value);
    }
    public TimeSpan PurchaseTime
    {
        get => GetValue<TimeSpan>(PurchaseTimeProperty);
        set => SetValue(PurchaseTimeProperty, value);
    }
    public string? PurchaseFrom
    {
        get => GetValue<string>(PurchaseFromProperty);
        set => SetValue(PurchaseFromProperty, value);
    }
    public string? Cost
    {
        get => GetValue<string?>(CostProperty);
        set => SetValue(CostProperty, value);
    }
    public string? Memo
    {
        get => GetValue<string>(MemoProperty);
        set => SetValue(MemoProperty, value);
    }

    string? selectedLocation;
    int? selectedCategoryId;

    public AddFlowerPage(FlowerDatabase database, ILogger logger) : base(database, logger)
    {
        var now = DateTime.Now;
        PurchaseDate = now.Date;
        PurchaseTime = now.TimeOfDay;

        InitializeComponent();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

#if DEBUG
        MainThread.BeginInvokeOnMainThread(() => CurrentLocation = new Location(29.56128954116272, 106.5447724580102));
#else
        MainThread.BeginInvokeOnMainThread(GetLocation);
    }

    bool accuracyLocation = false;

    async void GetLocation()
    {
        var location = await GetLastLocationAsync();
        CurrentLocation = location;

        if (!accuracyLocation)
        {
            location = await GetCurrentLocationAsync();
            CurrentLocation = location;

            accuracyLocation = location != null;
        }
#endif
    }

    private async void ButtonTakePhoto_Clicked(object sender, EventArgs e)
    {
        if (MediaPicker.Default.IsCaptureSupported)
        {
            var photo = await TakePhoto();

            if (photo != null)
            {
                string cache = await CacheFileAsync(photo);
                Cover = cache;
            }
        }
        else
        {
            await this.AlertError(L("notSupportedCapture", "Your device does not support taking photos."));
        }
    }

    private async void ButtonSelectPhoto_Clicked(object sender, EventArgs e)
    {
        var photo = await MediaPicker.Default.PickPhotoAsync();

        if (photo != null)
        {
            string cache = await CacheFileAsync(photo);
            Cover = cache;
        }
    }

    private void ButtonClearCover_Clicked(object sender, EventArgs e)
    {
        Cover = null;
    }

    private async void FlowerCategory_Tapped(object sender, EventArgs e)
    {
        var categories = Database.Categories;
        if (categories == null)
        {
            return;
        }

        var page = new ItemSelectorPage<int, IdTextItem<int>>(
            L("flowerCategory", "Flower category"),
            categories.Select(c => new IdTextItem<int>
            {
                Id = c.Key,
                Text = c.Value.Name,
                Detail = c.Value.Description
            }).ToArray(),
            selected: selectedCategoryId != null ?
                new[] { selectedCategoryId.Value } :
                Array.Empty<int>(),
            detail: nameof(IdTextItem<int>.Detail));
        page.Selected += FlowerCategory_Selected;

        await Navigation.PushAsync(page);
    }

    private void FlowerCategory_Selected(object? sender, IdTextItem<int> category)
    {
        selectedCategoryId = category.Id;
        Category = category.Text;
    }

    private async void Save_Clicked(object sender, EventArgs e)
    {
        var name = FlowerName;
        if (string.IsNullOrEmpty(name))
        {
            await this.Alert(Title, L("flowerNameRequired", "Flower name is required."));
            return;
        }

        // TODO: selectedLocation
        var location = FlowerLocation;
        if (string.IsNullOrEmpty(location))
        {
            await this.Alert(Title, L("locationRequired", "Location is required."));
            return;
        }

        if (selectedCategoryId == null)
        {
            await this.Alert(Title, L("flowerCategoryRequired", "Flower category is required."));
            return;
        }

        var purchaseDate = new DateTimeOffset((PurchaseDate + PurchaseTime).ToUniversalTime());
        var purchaseFrom = PurchaseFrom;
        if (!decimal.TryParse(Cost, out decimal cost) || cost < 0)
        {
            await this.Alert(Title, L("costInvalid", "Cost must be a positive number."));
            return;
        }

        var memo = Memo;

        var item = new FlowerItem
        {
            Name = name,
            CategoryId = selectedCategoryId.Value,
            DateBuyUnixTime = purchaseDate.ToUnixTimeMilliseconds(),
            Purchase = purchaseFrom,
            Cost = cost,
            Memo = memo,
            OwnerId = AppResources.User.Id
        };
        var loc = CurrentLocation;
        if (loc != null)
        {
            item.Latitude = loc.Latitude;
            item.Longitude = loc.Longitude;
        }

        var cover = Cover;
        if (cover != null)
        {
            item.Photos = new[] { new PhotoItem { Url = cover } };
        }

        await Loading(true);
        _ = Task.Run(async () =>
        {
            try
            {
                await DoAddFlowerAsync(item);
            }
            catch (Exception ex)
            {
                this.LogError(ex, $"error occurs while adding flower, {item}");
                await this.AlertError(L("failedAddFlower", "Failed to add flower, {error}, please try again later.").Replace("{error}", ex.Message));
            }
        });
    }

    async Task DoAddFlowerAsync(FlowerItem item)
    {
        var data = new MultipartFormDataContent
        {
            { new StringContent(item.CategoryId.ToString()), "categoryId" },
            { new StringContent(item.Name), "name" },
            { new StringContent(item.DateBuyUnixTime.ToString()), "dateBuy" }
        };
        if (item.Cost != null)
        {
            data.Add(new StringContent($"{item.Cost}"), "cost");
        }
        if (item.Purchase != null)
        {
            data.Add(new StringContent(item.Purchase), "purchase");
        }
        if (item.Memo != null)
        {
            data.Add(new StringContent(item.Memo), "memo");
        }
        if (item.Latitude != null && item.Longitude != null)
        {
            data.Add(new StringContent($"{item.Latitude}"), "lat");
            data.Add(new StringContent($"{item.Longitude}"), "lon");
        }
        if (item.Photos?.Length > 0)
        {
            data.Add(new StreamContent(File.OpenRead(item.Photos[0].Url)), "cover");
        }
        var result = await UploadAsync<FlowerItem>("api/flower/add", data);

        this.LogInformation($"upload successfully, {result}");
    }
}

class CoverConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is string s && !string.IsNullOrEmpty(s))
        {
            return s;
        }
        return "empty_flower.jpg";
    }

    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

record GeoResult
{
    [JsonPropertyName("address")]
    public AddressResult? Address { get; init; }

    [JsonPropertyName("error")]
    public ErrorResult? Error { get; init; }
}

record ErrorResult
{
    [JsonPropertyName("code")]
    public int Code { get; init; }

    [JsonPropertyName("message")]
    public string Message { get; init; } = null!;
}

record AddressResult
{
    [JsonPropertyName("City")]
    public string? City { get; init; }

    [JsonPropertyName("District")]
    public string? District { get; init; }

    [JsonPropertyName("CntryName")]
    public string? CountryName { get; init; }

    [JsonPropertyName("CountryCode")]
    public string? CountryCode { get; init; }

    [JsonPropertyName("Region")]
    public string? Region { get; init; }

    [JsonPropertyName("RegionAbbr")]
    public string? RegionAbbr { get; init; }
}