using System.Text.RegularExpressions;
using InnovEnergy.API.DataModel;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;

namespace InnovEnergy.API;

public class VrmLink : IDisposable
{
    private static readonly Regex RxSerial = new Regex(@"\s*\(\s*20\d\d-\d\d\d\d\d(\.\d)?\s*\)\s*", RegexOptions.Compiled | RegexOptions.Singleline);

    private Vrm Vrm { get; }

    private VrmLink(Vrm vrm)
    {
        Vrm = vrm;
    }

    public static async Task<VrmLink> Login()
    {
        const String vrmUser = "victron@innov.energy";
        const String vrmPwd  = "NnoVctr201002";

        var vrm = await Vrm.Login(vrmUser, vrmPwd);

        return new VrmLink(vrm);
    }

    public async Task SyncWithVrm(Data data)
    {

        var installationDetails = Vrm.GetInstallationDetailsAsync();

        // var vpnIpLookup     = Vpn.CreateVpnIpLookup();
        // var vpnOnlineLookup = Vpn.CreateVpnOnlineLookup();

        var vpnIpLookup = (String s) => "127.0.0.1";
        var vpnOnlineLookup = (String s) => true;


        var vrmInstallationsById = data
            .Root
            .DescendantInstallations()
            .Where(d => (d.Element as VrmInstallation)?.UniqueId != null)
            .ToDictionary(d => d.Element.CastTo<VrmInstallation>().UniqueId!);

        List<User> DeleteExistingInstallation(InstallationDetails details)
        {
            vrmInstallationsById.TryGetValue(details.Identifier, out var existing);

            if (existing is null)
                return new List<User>();

            // if it already exists, delete it, but keep its users

            var installation = existing.Installation()!;
            var users = installation.Users;
            installation.Users = new List<User>();
            existing.Delete();

            return users;
        }


        // SYNC
        await foreach (var i in installationDetails)
        {
            if (!i.IsActive) continue;

            var (name, path, ieSerial) = ParseVrmName(i);

            var users = DeleteExistingInstallation(i);

            var installation = new VrmInstallation
            {
                Name            = name,
                Users           = users,
                IeSerial        = ieSerial,
                VrmId           = i.IdSite,
                UniqueId        = i.Identifier,
                FirmwareVersion = i.FirmwareVersion,
                SystemType      = i.SystemType,
                MachineSerial   = i.MachineSerial,
                MachineName     = i.MachineName,   // type: VenusGx, CCGX, etc.
                Tags            = i.Tags,
                VpnIp           = vpnIpLookup(i.MachineSerial),
                Online          = vpnOnlineLookup(i.MachineSerial),
            };


            if (i.MachineName is not null)
                await Vrm.AddTags(i.IdSite, i.MachineName);

            var folder = data.GetOrCreateFolder(path);
            folder.Add(installation);
        }

        // SORT
        foreach (var folder in data.Root.DescendantFolders().Select(d => d.Element).OfType<Folder>())
        {
            folder.Folders.Sort();
            folder.Installations.Sort();
            folder.Users.Sort();

            var grouped = folder.Installations.OfType<VrmInstallation>().GroupBy(i=>i.Name);

            foreach (var g in grouped.Where(g => g.Count() > 1))
            foreach (var (vrmInstallation, n) in g.OrderBy(i => i.UniqueId).Select((i, n) => (i, n)))
            {
                vrmInstallation.Name += $" ({n + 1})";
            }

            foreach (var installation in folder.Installations)
                installation.Users.Sort();
        }


    }

    private static (String name, IReadOnlyList<String> path, String? serial) ParseVrmName(InstallationDetails installationDetails)
    {
        var fullName = installationDetails.Name;

        var match = RxSerial.Match(fullName); // extract IeSerial from _entire_ fullname

        var serial = match.Success
            ? match.Value
            : null;

        fullName = RxSerial.Replace(fullName, "");

        var split = fullName.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);

        var path = split.Length > 1
            ? split.Skip(1).Reverse().ToArray(split.Length - 1)
            : new[] { "ie" };

        var name = split.Length >= 1
            ? split[0]
            : installationDetails.Identifier;

        return (name, path, serial);
    }

    public void Dispose() => Vrm.Dispose();
}