using PacketDotNet;
using Parsing_Packets.Models;
using SharpPcap;
using System.Net.NetworkInformation;
using System.Text;
using System.Text.Json;

namespace Parsing_Packets.Helpers;

internal static class HelperPhysicalAddress
{

    private static string GetPhysicalAddress(PhysicalAddress physicalAddress, char c)
    {
        StringBuilder results = new();
        string d = physicalAddress.ToString().ToLower();
        for (int i = 0; i < d.Length; i += 2)
            _ = results.Append(d[i]).Append(d[i + 1]).Append(c);
        results = results.Remove(results.Length - 1, 1);
        return results.ToString();
    }

    private static string GetPhysicalAddress(PhysicalAddress physicalAddress) =>
        $"{GetPhysicalAddress(physicalAddress, '-')} | {GetPhysicalAddress(physicalAddress, ':')}";

    private static void AddPacket(PhysicalAddressConfiguration physicalAddressConfiguration, ILogger<Worker> logger, string file, string physicalAddress, string ipv4Address, bool isSuggestion)
    {
        List<string>? collection;
        Dictionary<string, List<string>> keyValuePairs = GetKeyValuePairs(file);
        if (!keyValuePairs.TryGetValue(physicalAddress, out collection))
        {
            keyValuePairs.Add(physicalAddress, []);
            if (!keyValuePairs.TryGetValue(physicalAddress, out collection))
                throw new Exception();
        }
        if (ipv4Address.StartsWith(physicalAddressConfiguration.IPV4Filter) && (collection.Count < 2 || (!isSuggestion && collection[^1] != ipv4Address)))
        {
            logger.LogInformation("");
            if (collection.Count == 2)
                collection[1] = DateTime.Now.ToString("yyyy-MM-dd_HH-mm");
            else if (collection.Count == 0)
            {
                collection.Add($"block-{physicalAddress[^2..]}");
                collection.Add(DateTime.Now.ToString("yyyy-MM-dd_HH-mm"));
            }
            if (collection.Count > 1)
            {
                logger.LogInformation(collection[0]);
                logger.LogInformation(collection[1]);
            }
            if (collection.Remove(ipv4Address))
                collection[1] = DateTime.Now.ToString("yyyy-MM-dd_HH-mm");
            collection.Add(ipv4Address);
            logger.LogInformation(ipv4Address);
            logger.LogInformation(physicalAddress);
            string json = JsonSerializer.Serialize(keyValuePairs, HelperPhysicalAddressDictionarySourceGenerationContext.Default.DictionaryStringListString);
            File.WriteAllText(file, json);
        }
    }

    private static void AddArpPacket(PhysicalAddressConfiguration physicalAddressConfiguration, ILogger<Worker> logger, string file, EthernetPacket ethernetPacket, ArpPacket arpPacket)
    {
        string ipv4Address = arpPacket.SenderProtocolAddress.ToString();
        string physicalAddress = GetPhysicalAddress(ethernetPacket.SourceHardwareAddress);
        AddPacket(physicalAddressConfiguration, logger, file, physicalAddress, ipv4Address, isSuggestion: false);
    }

    private static void AddDhcpPacket(PhysicalAddressConfiguration physicalAddressConfiguration, ILogger<Worker> logger, string file, DhcpV4Packet dhcpV4Packet)
    {
        string ipv4Address;
        string physicalAddress;
        if (dhcpV4Packet.MessageType is DhcpV4MessageType.Request or DhcpV4MessageType.Ack)
        {
            ipv4Address = dhcpV4Packet.ClientAddress.ToString();
            physicalAddress = GetPhysicalAddress(dhcpV4Packet.ClientHardwareAddress);
            bool isSuggestion = dhcpV4Packet.MessageType is DhcpV4MessageType.Request;
            AddPacket(physicalAddressConfiguration, logger, file, physicalAddress, ipv4Address, isSuggestion);
        }
        else
        {
            ipv4Address = dhcpV4Packet.ClientAddress.ToString();
            physicalAddress = GetPhysicalAddress(dhcpV4Packet.ClientHardwareAddress);
            AddPacket(physicalAddressConfiguration, logger, file, physicalAddress, ipv4Address, isSuggestion: true);
        }
    }

    private static void AddIpv4Packet(PhysicalAddressConfiguration physicalAddressConfiguration, ILogger<Worker> logger, string file, EthernetPacket ethernetPacket, IPv4Packet ipv4Packet)
    {
        string ipv4Address = ipv4Packet.SourceAddress.ToString();
        string physicalAddress = GetPhysicalAddress(ethernetPacket.SourceHardwareAddress);
        AddPacket(physicalAddressConfiguration, logger, file, physicalAddress, ipv4Address, isSuggestion: true);
    }

    private static void ParseEthernetPacket(PhysicalAddressConfiguration physicalAddressConfiguration, ILogger<Worker> logger, string file, EthernetPacket ethernetPacket)
    {
        for (int i = 0; i < 1; i++)
        {
            if (physicalAddressConfiguration.UseARP && ethernetPacket?.PayloadPacket is ArpPacket arpPacket)
            {
                if (arpPacket.Operation != ArpOperation.Response)
                    continue;
                AddArpPacket(physicalAddressConfiguration, logger, file, ethernetPacket, arpPacket);
            }
            else if (ethernetPacket?.PayloadPacket is IPv4Packet ipv4Packet)
            {
                if (ipv4Packet.SourceAddress.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
                    continue;
                if (!ipv4Packet.SourceAddress.ToString().StartsWith(physicalAddressConfiguration.IPV4Filter))
                    continue;
                if (ipv4Packet.PayloadPacket is UdpPacket udpPacket && udpPacket.PayloadPacket is DhcpV4Packet dhcpV4Packet)
                    AddDhcpPacket(physicalAddressConfiguration, logger, file, dhcpV4Packet);
                else
                    AddIpv4Packet(physicalAddressConfiguration, logger, file, ethernetPacket, ipv4Packet);
            }
        }
    }

    private static Dictionary<string, List<string>> GetKeyValuePairs(string file)
    {
        Dictionary<string, List<string>> keyValuePairs;
        if (!File.Exists(file))
            keyValuePairs = [];
        else
        {
            string json = File.ReadAllText(file);
            keyValuePairs = JsonSerializer.Deserialize(json, HelperPhysicalAddressDictionarySourceGenerationContext.Default.DictionaryStringListString) ?? [];
        }
        return keyValuePairs;
    }

    internal static bool ParsePackets(AppSettings appSettings, ILogger<Worker> logger, CancellationToken stoppingToken)
    {
        ILiveDevice? liveDevice = null;
        Version version = Pcap.SharpPcapVersion;
        CaptureDeviceList devices = CaptureDeviceList.Instance;
        logger.LogInformation("PacketDotNet example using SharpPcap {version}", version);
        if (devices.Count < 1)
            logger.LogInformation("No devices were found on this machine");
        else
        {
            Packet packet;
            RawCapture rawCapture;
            GetPacketStatus status;
            if (!Directory.Exists(appSettings.PhysicalAddressConfiguration.Directory))
                _ = Directory.CreateDirectory(appSettings.PhysicalAddressConfiguration.Directory);
            string file = Path.Combine(appSettings.PhysicalAddressConfiguration.Directory, ".json");
            logger.LogInformation("");
            logger.LogInformation("The following devices are available on this machine:");
            logger.LogInformation("----------------------------------------------------");
            logger.LogInformation("");
            foreach (ILiveDevice? device in devices)
            {
                logger.LogInformation("{Name} {Description}", device.Name, device.Description);
                if (device.Name != appSettings.PhysicalAddressConfiguration.DeviceName)
                    continue;
                liveDevice = device;
            }
            if (liveDevice is null)
                logger.LogInformation("No devices matched {DeviceName}", appSettings.PhysicalAddressConfiguration.DeviceName);
            else
            {
                logger.LogInformation("");
                liveDevice.Open(DeviceModes.Promiscuous, appSettings.PhysicalAddressConfiguration.ReadTimeoutMilliseconds);
                logger.LogInformation("-- Listening on {Name} {Description}", liveDevice.Name, liveDevice.Description);
                while (!stoppingToken.IsCancellationRequested)
                {
                    status = liveDevice.GetNextPacket(out PacketCapture e);
                    if (status != GetPacketStatus.PacketRead)
                        continue;
                    rawCapture = e.GetPacket();
                    packet = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data);
                    if (packet is not EthernetPacket ethernetPacket)
                        continue;
                    ParseEthernetPacket(appSettings.PhysicalAddressConfiguration, logger, file, ethernetPacket);
                }
            }
        }
        return true;
    }

}