using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using PhilExampleCrawler.Common.Models;
using PhilExampleCrawler.Core;
using PhilExampleCrawler.Core.Abstractions.Interfaces;
using Xamarin.Forms;

namespace PhilExample.Services
{
    public interface IForegroundCrawlDelegate
    {
        string GetForegroundNotificationTitle { get; }
        string GetForegroundNotificationDescription { get; }
        void Crawl();
    }

    internal interface ICrawlService_SH
    {
        IReadOnlyList<CrawlingSession> ActiveCrawlers { get; }
        /// <summary> Starts a crawling session that repeatedly crawls based on given parameters </summary>
        /// <param name="crawlSearchParams">Parameters that define the reoccuring crawling event</param>
        /// <param name="interval_sec">Time between crawling events</param>
        /// <returns>ID of started session</returns>
        int StartCrawlingSession(CrawlSearchParams crawlSearchParams, int interval_sec);
        void StopCrawlingSession(int sessionID);
        event EventHandler<Insertion> OnNewInsertionFound;
        void StopCrawler();
    }

    public class CrawlService_SH : ICrawlService_SH, IForegroundCrawlDelegate
    {
        public IReadOnlyList<CrawlingSession> ActiveCrawlers => new List<CrawlingSession>(_activeCrawlers);
        public event EventHandler<Insertion> OnNewInsertionFound;


        private readonly IForegroundCrawlService _fgService = DependencyService.Get<IForegroundCrawlService>();
        private readonly List<CrawlingSession> _activeCrawlers = new List<CrawlingSession>();
        static int _lastCrawlerID = 0;


        private void Crawler_OnNewInsertionFound(object sender, Insertion e)
        {
            OnNewInsertionFound?.Invoke(sender, e);
        }

        public void Crawl()
        {
            foreach (var crawlSess in _activeCrawlers.ToList()) //TODO: ToList() is to circumvent collection modification while still in this loop, TRY using ActiveCrawlers
                if (crawlSess.IntervalPassed)
                {
                    crawlSess.LastCrawlAt = DateTime.Now;
                    Console.WriteLine(GetTimeStamp() + " started crawl for id: " + crawlSess.ID);
                    try
                    {
                        crawlSess.Crawler.Crawl(crawlSess.CrawlSearchParams, 1000 + 500);
                    }
                    catch (WebException webEx)
                    {
                        //TODO: if repeatedly throws for same job or if it happens too often
                        //  => either inform or increase timeout (at runtime + until limit maybe?)
                        if (!(webEx.Status == WebExceptionStatus.Timeout))
                            throw;
                        else
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.Write("[TODO: LOGGING] "); Console.ForegroundColor = ConsoleColor.White;
                            Console.WriteLine("ICrawlService.Crawl() timed out for crawlSession ID " + crawlSess.ID);
                        }
                    }
                    Console.WriteLine(GetTimeStamp() + " finished crawl for id:  " + crawlSess.ID);
                }
        }

        public int StartCrawlingSession(CrawlSearchParams crawlSearchParams, int interval_sec)
        {
            var crawler = Crawler.CreateInstance();
            var crawlSess = new CrawlingSession(++_lastCrawlerID, crawler, crawlSearchParams, interval_sec);
            crawler.OnNewInsertionFound += Crawler_OnNewInsertionFound;
            _activeCrawlers.Add(crawlSess);

            _fgService.Start(this);
            return crawlSess.ID;
        }


        public void StopCrawlingSession(int sessionID)
        {
            if (_activeCrawlers.Any(x => x.ID == sessionID))
                _activeCrawlers.Remove(_activeCrawlers.First(x => x.ID == sessionID));

            UpdateForegroundService();
        }

        public void StopCrawler()
        {
            _activeCrawlers.Clear();
            UpdateForegroundService();
        }


        private void UpdateForegroundService()
        {
            if (_activeCrawlers.Count == 0)
                _fgService.Stop();
            else
                _fgService.UpdateNotification();
        }


        #region delegate
        public string GetForegroundNotificationTitle
            => _activeCrawlers.Count == 1 ? "1 aktive Suche"
                                          : _activeCrawlers.Count + " aktive Suchen";
        public string GetForegroundNotificationDescription
            => string.Join(Environment.NewLine, _activeCrawlers.Select(x => ToNotificationString(x.CrawlSearchParams)));

        private static string ToNotificationString(CrawlSearchParams c)
        {
            return $"'{c.KeyWords}' in {c.LocationStr} ({c.Radius}km) [{c.CategoryID}]";
        }
        #endregion


        private static string GetTimeStamp() => "[" + DateTime.Now.TimeOfDay.ToString().Substring(0, 8) + "]";
    }

    public class CrawlingSession
    {
        public int ID { get; }
        public ICrawlingService_HAP Crawler { get; }
        public CrawlSearchParams CrawlSearchParams { get; }
        public int CrawlInterval_Sec { get; }

        public DateTime LastCrawlAt { get; set; }
        /// <summary> Returns if "(DateTime.Now - LastCrawlAt).TotalSeconds > CrawlInterval_Sec" </summary>
        public bool IntervalPassed => (DateTime.Now - LastCrawlAt).TotalSeconds > CrawlInterval_Sec;


        public CrawlingSession(int id, ICrawlingService_HAP crawler, CrawlSearchParams crawlSearchParams, int interval_sec)
        {
            ID = id;
            Crawler = crawler;
            CrawlSearchParams = crawlSearchParams;
            CrawlInterval_Sec = interval_sec;
            LastCrawlAt = DateTime.MinValue; //makes IntervalPassed return true and therefore force a direct crawl when created
        }
    }
}
