using ISPChk.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ISPChk
{
    public class NetworkTest : INetworkTest
    {
        private ISPChkContext _context;
        private List<Task> runningTasks;

        public NetworkTest() {
            _context = CreateDbContext();
            System.Diagnostics.Debug.WriteLine("NetworkTest instantiated.");
        }

        public async void LoadHostsFromDatabase()
        {
            List<Host> hosts = await _context.Hosts.ToListAsync();
            foreach (Host host in hosts)
            {
                AddHost(host);
            }
        }

        private ISPChkContext CreateDbContext()
        {
            var optionsBuilder = new DbContextOptionsBuilder<ISPChkContext>();
            optionsBuilder.UseSqlite("Data Source=ispchk.db");
            var ctx = new ISPChkContext(optionsBuilder.Options);
            System.Diagnostics.Debug.WriteLine("DbContext created.");
            ctx.Database.EnsureCreated();
            return ctx;
        }

        private void StartPing(Host host)
        {
            var t = Task.Run(async () => {

                // Do not use the outer context, not threadsafe?
                var ctx = CreateDbContext();
                var h = await ctx.Hosts.FindAsync(host.HostId);
                ctx.Entry(h).Collection(hst => hst.PingItems).Query().FirstOrDefault();

                IPAddress pingAddress;


                if (!IPAddress.TryParse(h.HostName, out pingAddress))
                {
                    System.Diagnostics.Debug.WriteLine("Resolving "+h.HostName);
                    IPHostEntry hostInfo = Dns.GetHostEntry(h.HostName);
                    foreach(var a in hostInfo.AddressList)
                    {
                        if((a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 && h.IPv6 == true) ||
                            a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && h.IPv6 == false)
                        {
                            pingAddress = a;
                            break;
                        }
                    }
                }

                if(pingAddress == null)
                {
                    System.Diagnostics.Debug.WriteLine("No address to ping found...");
                    return;
                }

                Ping pingSender = new Ping();

                // Create a buffer of 32 bytes of data to be transmitted.
                string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
                byte[] buffer = Encoding.ASCII.GetBytes(data);

                // Wait 10 seconds for a reply.
                int timeout = 3000;

                // Set options for transmission:
                // The data can go through 64 gateways or routers
                // before it is destroyed, and the data packet
                // cannot be fragmented.
                PingOptions options = new PingOptions(64, true);

                System.Diagnostics.Debug.WriteLine("Initiate ping to " + pingAddress);

                while (true)
                {
                    int numPings = 5;
                    long min = timeout + 1;
                    long max = 0;
                    long avg = 0;
                    int successes = 0;
                    int failures = 0;
                    for (var i = 0; i < numPings; ++i)
                    {
                        try {
                            // Send the request.
                            PingReply reply = pingSender.Send(pingAddress, timeout, buffer, options);

                            if (reply.Status == IPStatus.Success)
                            {
                                successes++;
                                //System.Diagnostics.Debug.WriteLine("Reply from " + reply.Address.ToString() +
                                //    ": bytes=" + reply.Buffer.Length +
                                //    " time=" + reply.RoundtripTime + "ms");
                                avg += reply.RoundtripTime;
                                if (reply.RoundtripTime < min)
                                {
                                    min = reply.RoundtripTime;
                                }
                                if (reply.RoundtripTime > max)
                                {
                                    max = reply.RoundtripTime;
                                }
                                //System.Diagnostics.Debug.WriteLine("Time to live: {0}", reply.Options.Ttl);
                                //System.Diagnostics.Debug.WriteLine("Don't fragment: {0}", reply.Options.DontFragment);
                            }
                                else
                            {
                                System.Diagnostics.Debug.WriteLine(reply.Status);
                                failures++;
                            }
                        } catch(Exception e) {
                            System.Diagnostics.Debug.WriteLine("Found exception while pinging"+e);
                            failures++;
                        }
                        Thread.Sleep(500);
                    }
                    if(successes > 0)
                        avg = avg / successes;
                    // FIXME: This is dirty but I don't want the graph to explode
                    if (min > timeout)
                        min = 0;
                    System.Diagnostics.Debug.WriteLine(h.HostName+": "+"min:" + min + " max:" + max + " avg:" + avg);
                    PingItem pi = new PingItem { Date = DateTimeOffset.UtcNow.DateTime, Min = min, Max = max, Avg = avg, Failures = failures };
                    h.PingItems.Add(pi);
                    ctx.SaveChanges();
                    // Throttle if everything is ok
                    if(successes == numPings)
                        Thread.Sleep(5000);
                }
            });
        }

        public void AddHost(Host host)
        {
            System.Diagnostics.Debug.WriteLine("Host added!");
            if(host.Ping)
                StartPing(host);
        }
    }
}
