using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Geocoding;
using Geocoding.Google;
using InABox.Clients;
using InABox.Core;
using Nominatim.API.Geocoders;
using Nominatim.API.Models;
using Nominatim.API.Web;
using Syncfusion.UI.Xaml.Maps;
using Address = InABox.Core.Address;
using Point = System.Windows.Point;

namespace InABox.Wpf.DynamicGrid;

public partial class GeofenceEditor : Window
{
    public Address Address { get; private set; }
    
    GeoFenceDefinition _definition = null;

    private ImageryLayer ImageryLayer;
    public MapPolygon Polygon;
    
    public GeofenceEditor(Address address, bool canEdit)
    {
        Address = address;
        _definition = Serialization.Deserialize<GeoFenceDefinition>(address.Geofence) ?? new GeoFenceDefinition();
        
        InitializeComponent();

        ImageryLayer = string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey)
            ? new ImageryLayer()
            : new GoogleImageryLayer() { Type = GoogleImageryLayerType.Satellite };
        ImageryLayer.Radius = 10;
        
        Map.Layers.Add(ImageryLayer);

        Polygon = new MapPolygon()
        {
            Fill = new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 },
            Stroke = new SolidColorBrush(Colors.Firebrick),
            StrokeThickness = 0.75,
        };
        var layer = new SubShapeFileLayer() { MapElements = new ObservableCollection<MapElement>([Polygon]) }; 
        ImageryLayer.SubShapeFileLayers = new ObservableCollection<SubShapeFileLayer>([layer]);
        
        SetGeometry.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed;
        SetRadius.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed;
        SearchBar.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed;
        
        Street.Text = address.Street;
        City.Text = address.City;
        State.Text = address.State;
        PostCode.Text = Address.PostCode;

        Title = $"Geofence Editor ({(string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey) ? "OSM" : "Google")})";

        if (canEdit && string.IsNullOrWhiteSpace(Address.Geofence))
            Task.Run(CheckAddress);
        else
            SetupMap();
        
    }
    
    private class HttpClientFactory : IHttpClientFactory
    {
        private static HttpClient? _client;
        public HttpClient CreateClient(string name)
        {
                
            _client ??= new HttpClient();
            return _client;
        }
    }
    
    private static HttpClientFactory? _httpClientFactory = null;

    private async Task CheckAddress()
    {
        if (!string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey))
        {
        
            IGeocoder geocoder = new GoogleGeocoder(CoreUtils.GoogleAPIKey);
            try
            {
                var matches =
                    await geocoder.GeocodeAsync(
                        $"{Address.Street}, {Address.City} {Address.State} {Address.PostCode} Australia");
                var match = matches.FirstOrDefault();
                if (match != null)
                {
                    Address.Location.Longitude = match.Coordinates.Longitude;
                    Address.Location.Latitude = match.Coordinates.Latitude;
        
                    if (match is GoogleAddress gAdd)
                    {
                        _definition.Coordinates.Clear();
                        _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude,
                            gAdd.Bounds.NorthEast.Longitude));
                        _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude,
                            gAdd.Bounds.SouthWest.Longitude));
                        _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.SouthWest.Latitude,
                            gAdd.Bounds.SouthWest.Longitude));
                        _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.SouthWest.Latitude,
                            gAdd.Bounds.NorthEast.Longitude));
                        _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude,
                            gAdd.Bounds.NorthEast.Longitude));
                    }
                    else
                        SquareFence(20.0);
        
                    Address.Geofence = Serialization.Serialize(_definition);
                }
                else
                {
                    Address.Location.Longitude = 0.0;
                    Address.Location.Latitude = 0.0;
                    Address.Geofence = string.Empty;
                }
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, ClientFactory.UserID, $"{e.Message}\n{e.StackTrace}");
            }
        }
        else
        {
            _httpClientFactory ??= new HttpClientFactory();
            NominatimWebInterface intf = new NominatimWebInterface(_httpClientFactory);
            
            var searcher = new ForwardGeocoder(intf);

            var request = new ForwardGeocodeRequest()
            {
                StreetAddress = Address.Street,
                City = Address.City,
                // County="",
                State = Address.State,
                PostalCode = Address.PostCode,
                Country = "AU",
                //queryString = $"{Address.Street}, {Address.City}, {Address.State}, {Address.PostCode} Australia",
                //BreakdownAddressElements = true,
                ShowExtraTags = true,
                ShowAlternativeNames = true,
                ShowGeoJSON = true,
            };

            try
            {
                var matches = await searcher.Geocode(request);
                var match = matches.FirstOrDefault();
                if (match != null)
                {
                    Address.Location.Longitude = match.Longitude;
                    Address.Location.Latitude = match.Latitude;
                    if (match.BoundingBox.HasValue)
                    {
                        var bbox = match.BoundingBox.Value;
                        _definition.Coordinates.Clear();
                        _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.minLongitude));
                        _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.maxLongitude));
                        _definition.Coordinates.Add(new GeoPoint(bbox.maxLatitude, bbox.maxLongitude));
                        _definition.Coordinates.Add(new GeoPoint(bbox.maxLatitude, bbox.minLongitude));
                        _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.minLongitude));
                    }
                    else
                        SquareFence(20.0);

                    Address.Geofence = Serialization.Serialize(_definition);
                }
                else
                {
                    Address.Location.Longitude = 0.0;
                    Address.Location.Latitude = 0.0;
                    Address.Geofence = string.Empty;
                }
            }
        
            catch (Exception e)
            {
                Logger.Send(LogType.Error, ClientFactory.UserID, $"{e.Message}\n{e.StackTrace}");
            }
            
        }


        Dispatcher.BeginInvoke(SetupMap);

    }

    private void SquareFence(double side)
    {
        
        _definition.Coordinates.Clear();
        _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),0-(side/2.0)));
        _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),side/2.0));
        _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(side/2.0,side/2.0));
        _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(side/2.0,0-(side/2.0)));
        _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),0-(side/2.0)));
    }

    private void SetupMap()
    {
        CenterMap();

        RadiusSlider.ValueChanged -= RadiusSliderChanged;
        RadiusSlider.Value = 20.0;
        if (_definition.Coordinates.Count < 4)
            SquareFence(20.0);
        RadiusSlider.ValueChanged += RadiusSliderChanged;
        UpdateMap();
    }

    private void CenterMap()
    {
        ImageryLayer.Center = new Point(Address.Location.Latitude, Address.Location.Longitude);
        ImageryLayer.Markers = new CoreObservableCollection<MapMarker>([new MapMarker()
        {
            Latitude = $"{Address.Location.Latitude:F15}",
            Longitude = $"{Address.Location.Longitude:F15}"
        }]);
    }

    private void RadiusSliderChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        RecalculateSquareFence();
        UpdateMap();
    }

    private void RecalculateSquareFence()
    {
        SquareFence(RadiusSlider.Value);
        Address.Geofence = Serialization.Serialize(_definition);
    }

    private void UpdateMap()
    {
       
        Polygon.Points = new ObservableCollection<Point>(_definition.Coordinates.Select(p => new Point(p.Latitude, p.Longitude)));
        
    }
    
    private void ZoomIn_OnClick(object sender, RoutedEventArgs e)
    {
        Map.MaxZoom = Math.Min(20, Map.ZoomLevel + 1);
        Map.ZoomLevel = Math.Min(20, Map.ZoomLevel + 1);
        UpdateMap();
    }

    private void ZoomOut_OnClick(object sender, RoutedEventArgs e)
    {
        Map.MinZoom= Math.Max(5, Map.ZoomLevel - 1);
        Map.ZoomLevel = Math.Max(5, Map.ZoomLevel - 1);
        UpdateMap();
    }

    private enum GeoFenceAction
    {
        None,
        Polygon,
        Square,
        Coordinates
    }

    private GeoFenceAction _action = GeoFenceAction.None;

    private void UpdateButtons(GeoFenceAction action)
    {
        _action = action;
        SetGeometry.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Polygon;
        SetGeometry.Background = action == GeoFenceAction.Polygon
            ? Brushes.Yellow
            : Brushes.Silver;
        SetRadius.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Square;
        SetRadius.Background = action == GeoFenceAction.Square
            ? Brushes.Yellow
            : Brushes.Silver;
        RadiusSlider.Visibility = action == GeoFenceAction.Square 
            ? Visibility.Visible 
            : Visibility.Collapsed;
        SetCoordinates.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Coordinates;
        SetCoordinates.Background = action == GeoFenceAction.Coordinates
            ? Brushes.Yellow
            : Brushes.Silver;
    }
    
    private void SetGeometry_OnClick(object sender, RoutedEventArgs e)
    {
        if (_action == GeoFenceAction.Polygon)
            UpdateButtons(GeoFenceAction.None);
        else
        {
            UpdateButtons(GeoFenceAction.Polygon);
            _definition.Coordinates.Clear();
            UpdateMap();
        }
    }

    private void Map_OnMouseUp(object sender, MouseButtonEventArgs e)
    {
        var point = Mouse.GetPosition(Map);
        var latlon = ImageryLayer.GetLatLonFromPoint(point);
        var geopoint = new GeoPoint(latlon.Y, latlon.X);

        if (_action == GeoFenceAction.Polygon)
        {
            if (!_definition.Coordinates.Any())
                _definition.Coordinates.Add(geopoint.Copy());
            _definition.Coordinates.Insert(_definition.Coordinates.Count - 1, geopoint.Copy());
            Address.Geofence = Serialization.Serialize(_definition);
            UpdateMap();
            e.Handled = true;
        }
        else if (_action == GeoFenceAction.Coordinates)
        {
            Address.Location.Latitude = geopoint.Latitude;
            Address.Location.Longitude = geopoint.Longitude;
            CenterMap();
            UpdateMap();
            e.Handled = true;
        }

    }


    private void SetRadius_OnClick(object sender, RoutedEventArgs e)
    {
        if (_action == GeoFenceAction.Square)
            UpdateButtons(GeoFenceAction.None);
        else
        {
            UpdateButtons(GeoFenceAction.Square);

            RadiusSlider.ValueChanged -= RadiusSliderChanged;
            RadiusSlider.Value = 20.0;
            SquareFence(20.0);
            RadiusSlider.ValueChanged += RadiusSliderChanged;
            RecalculateSquareFence();
            UpdateMap();
        }
    }

    private void SearchAddress_Click(object sender, RoutedEventArgs e)
    {
        Task.Run(CheckAddress);
    }

    private void SetCoordinates_OnClick(object sender, RoutedEventArgs e)
    {
        if (_action == GeoFenceAction.Coordinates)
            UpdateButtons(GeoFenceAction.None);
        else
            UpdateButtons(GeoFenceAction.Coordinates);
    }
}