using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using PhilExample.Interfaces;
using PhilExampleCrawler.Common.Models;
using Xamarin.Forms;
using System.Linq;
using PhilExample.Models;
using PhilExample.UI.Pages;
using PhilExample.Utils;
using PhilExampleCrawler.Common.Interfaces;

namespace PhilExample.VMs
{
    [QueryProperty(nameof(CrawlSessionID), nameof(CrawlSessionID))]
    internal class AddCrawlSessionVM : BaseVM, ICallbackPageVM<CrawlSessionResponse>
    {
        private int? _crawlSessionID;
        public string CrawlSessionID 
        { 
            get => _crawlSessionID?.ToString() ?? null; 
            set
            {
                _crawlSessionID = string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value);
                OnPropertyChanged();
            }
        }


        private readonly IPhExStorage _storage = DependencyService.Get<IPhExStorage>();
        private readonly IUserService _userService = DependencyService.Get<IUserService>();
        private readonly ICrawlSessionService _crawlService = DependencyService.Get<ICrawlSessionService>();
        private readonly IExamplePageService _ekaService = DependencyService.Get<IExamplePageService>();

        public event EventHandler<CrawlSessionResponse> OnPopped;
        public ICommand CloseCommand { get; }
        public ICommand AddCommand { get; }
        public ICommand UpdateCommand { get; }
        public ICommand DeleteCommand { get; }
        public ICommand SelectCategoryCommand { get; }
        public ICommand SelectRadiusCommand { get; }

        private string _pageTitle = "Neuer Crawl-Auftrag";
        private string _keywords;
        private string _locationStr;
        private ICategory _selectedCat;
        private ICategory _selectedRadius;
        private string _minPrice;
        private string _maxPrice;
        private bool _isPrivate = true;
        private bool _isCommercial = true;

        private bool _isKeywordsFocused;

        private bool _isError_Keywords;


        public string PageTitle { get => _pageTitle; set => SetProperty(ref _pageTitle, value); }
        public string Keywords
        {
            get => _keywords; 
            set
            {
                SetProperty(ref _keywords, value);
                OnKeywordsChanged(value);
            }
        }

        public string LocationStr { get => _locationStr; set => SetProperty(ref _locationStr, value); }
        public ICategory SelectedCategory { get => _selectedCat; set => SetProperty(ref _selectedCat, value); }
        public ICategory SelectedRadius { get => _selectedRadius; set => SetProperty(ref _selectedRadius, value); }
        public string MinPrice { get => _minPrice; set => SetProperty(ref _minPrice, value); }
        public string MaxPrice { get => _maxPrice; set => SetProperty(ref _maxPrice, value); }
        public bool IsPrivate
        {
            get => _isPrivate;
            set
            {
                SetProperty(ref _isPrivate, value);
                EnsureOneTick(nameof(IsPrivate));
            }
        }
        public bool IsCommercial
        {
            get => _isCommercial;
            set
            {
                SetProperty(ref _isCommercial, value);
                EnsureOneTick(nameof(IsCommercial));
            }
        }

        public bool IsKeywordsFocused
        {
            get => _isKeywordsFocused;
            set
            {
                SetProperty(ref _isKeywordsFocused, value);
                ValidateKeywords();
            }
        }

        #region ValidationProperties
        public bool IsError_Keywords { get => _isError_Keywords; set => SetProperty(ref _isError_Keywords, value); }
        #endregion


        private bool _popupFinished = false;

        public AddCrawlSessionVM()
        {
            CloseCommand = new Command(async () => await CloseAsync());
            AddCommand = new Command(async () => await AddAsync());
            UpdateCommand = new Command(async () => await UpdateAsync());
            DeleteCommand = new Command(async () => await DeleteAsync());

            SelectCategoryCommand = new Command(async () => await GoToCategoryPageAsync());
            SelectRadiusCommand = new Command(async () => await GoToRadiusPageAsync());
            SelectedCategory = _ekaService.GetDefaultCategory();
            SelectedRadius = _ekaService.GetDefaultRadius();
        }

        public override async Task OnAppearingAsync()
        {
            if (AppearedOnce)
                return;
            AppearedOnce = true;

            await InitAsync();
        }

        private async Task InitAsync()
        {
            CrawlSession cs = _userService.CurrentUser.CrawlSessions.FirstOrDefault(x => x.ID == _crawlSessionID);
            if (cs != null)
            {
                PageTitle = "Crawl-Auftrag bearbeiten";
                Keywords = cs.SearchParams.KeyWords;
                LocationStr = cs.SearchParams.LocationStr;
                MinPrice = cs.MinPrice <= 0 ? "" : cs.MinPrice.ToString();
                MaxPrice = cs.MaxPrice <= 0 ? "" : cs.MaxPrice.ToString();
                IsPrivate = cs.IsPrivate;
                IsCommercial = cs.IsCommercial;

                if (cs.SearchParams.CategoryID > 0)
                {
                    var cats = await _storage.GetCategoriesAsync();
                    IEnumerable<ICategory> iCats = cats.Select(cat => cat as ICategory);
                    IEnumerable<ICategory> iSubCats = cats.SelectMany(cat => cat.SubCategories.Select(subCat => subCat as ICategory));
                    IEnumerable<ICategory> allCats = iCats.Concat(iSubCats);

                    ICategory selectedCat = allCats.FirstOrDefault(x => x.ID == cs.SearchParams.CategoryID);
                    if (selectedCat != null)
                    {
                        SelectedCategory = selectedCat;
                    }
                }

                if (cs.SearchParams.Radius > 0)
                {
                    var radii = await _storage.GetRadiiAsync();
                    var selectedRadius = radii.FirstOrDefault(x => x.ID == cs.SearchParams.Radius);
                    if (selectedRadius != null)
                        SelectedRadius = selectedRadius;
                }
            }
        }

        private async Task CloseAsync()
        {
            if (_popupFinished)
                return;
            _popupFinished = true;

            OnPopped?.Invoke(this, new CrawlSessionResponse(false, null));
            await Shell.Current.GoToAsync("..");
        }

        private async Task AddAsync()
        {
            if (_popupFinished)
                return;
            if (!ValidateKeywords())
                return;
            _popupFinished = true;

            CrawlSession newCS = GetCrawlSession();
            CrawlSession cs = await _crawlService.RegisterCrawlSessionAsync(_userService.CurrentUser.ID, 
                                                                            newCS);
            if (cs == null)
            {
                Console.WriteLine("TODO: Show Error");
                _popupFinished = false;
                return;
            }

            OnPopped?.Invoke(this, new CrawlSessionResponse(true, cs));
            await Shell.Current.GoToAsync("..");
        }

        private async Task UpdateAsync()
        {
            if (!_crawlSessionID.HasValue || _popupFinished)
                return;
            if (!ValidateKeywords())
                return;
            _popupFinished = true;

            CrawlSession csToUpdate = GetCrawlSession();
            bool updated = await _crawlService.UpdateCrawlSessionAsync(_userService.CurrentUser.ID,
                                                                       csToUpdate);

            if (!updated)
            {
                Console.WriteLine("TODO: Show Error");
                _popupFinished = false;
                return;
            }

            OnPopped?.Invoke(this, new CrawlSessionResponse(true, csToUpdate));
            await Shell.Current.GoToAsync("..");
        }

        private async Task DeleteAsync()
        {
            if (!_crawlSessionID.HasValue || _popupFinished)
                return;
            _popupFinished = true;

            bool deleted = await _crawlService.DeleteAsync(_crawlSessionID.Value);
            if(!deleted)
            {
                Console.WriteLine("TODO: Show Error");
                _popupFinished = false;
                return;
            }

            OnPopped?.Invoke(this, new CrawlSessionResponse(true, null));
            await Shell.Current.GoToAsync("..");
        }

        
        private async Task GoToCategoryPageAsync()
        {
            await Shell.Current.GoToAsync($"{nameof(SelectCategoryPage)}");

            var page = ShellExtensions.GetCurrentPage<SelectCategoryPage>();
            if (page != null)
            {
                ICategory cat = await page.PagePoppedTask;
                if (cat != null)
                {
                    SelectedCategory = cat;
                    OnPropertyChanged("SelectedCategory.Name");
                }
            }
        }

        private async Task GoToRadiusPageAsync()
        {
            await Shell.Current.GoToAsync($"{nameof(SelectRadiusPage)}");

            var page = ShellExtensions.GetCurrentPage<SelectRadiusPage>();
            if (page != null)
            {
                ICategory radius = await page.PagePoppedTask;
                if (radius != null)
                {
                    SelectedRadius = radius;
                }
            }
        }


        private async void OnKeywordsChanged(string keywords)
        {
            if (!IsKeywordsFocused || string.IsNullOrEmpty(keywords))
                return;

            var pickerItems = await _ekaService.GetLocationSuggestionsAsync(keywords);
        }


        private CrawlSession GetCrawlSession()
        {
            int crawlSessionID = _crawlSessionID ?? -1;
            CrawlSearchParams csp = GetCrawlSearchParams();
            int enteredMinPrice = GetPrice(MinPrice);
            int enteredMaxPrice = GetPrice(MaxPrice);
            bool isPrivate = IsPrivate;
            bool isCommercial = IsCommercial;
            return new CrawlSession(crawlSessionID,
                                    csp,
                                    Math.Min(enteredMinPrice, enteredMaxPrice),
                                    Math.Max(enteredMinPrice, enteredMaxPrice),
                                    isPrivate, isCommercial);
        }

        private CrawlSearchParams GetCrawlSearchParams()
        {
            int catID = SelectedCategory?.ID ?? -1;
            int radiusID = SelectedRadius?.ID ?? 0;
            var csp = new CrawlSearchParams(Keywords, LocationStr, catID, radiusID);
            return csp;
        }

        private int GetPrice(string price)
        {
            if (string.IsNullOrEmpty(price))
                return -1;
            if (int.TryParse(price, out int priceInt))
                return priceInt;
            return -1;
        }


        #region Validation
        bool _firstValid = true;
        private bool ValidateKeywords()
        {
            if (_firstValid)
            {
                _firstValid = false;
                return true;
            }

            if (IsKeywordsFocused)
                IsError_Keywords = false;
            else
            {
                if (string.IsNullOrEmpty(Keywords) || Keywords.Length < 2)
                    IsError_Keywords = true;
                else
                    IsError_Keywords = false;
            }

            return !IsError_Keywords;
        }

        private void EnsureOneTick(string setBy)
        {
            if (setBy == nameof(IsPrivate) && !IsPrivate)
                IsCommercial = true;
            else if (setBy == nameof(IsCommercial) && !IsCommercial)
                IsPrivate = true;
        }
        #endregion
    }
}
