using System.Diagnostics.CodeAnalysis;
using System.IO.Enumeration;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Web;
using CliWrap;
using CliWrap.Buffered;
using InnovEnergy.App.RemoteSupportConsole;
using InnovEnergy.Lib.Utils;
using InnovEnergy.Lib.Victron.VictronVRM;
using SQLite;
using static System.Text.Json.JsonSerializer;
using static InnovEnergy.App.VrmGrabber.Database.Systemd;
using Installation = InnovEnergy.App.VrmGrabber.DataTypes.Installation;
using VrmInstallation = InnovEnergy.Lib.Victron.VictronVRM.Installation;
using FILE=System.IO.File;


namespace InnovEnergy.App.VrmGrabber.Database;

public static class Systemd
{
    [DllImport("libsystemd.so.0", CharSet = CharSet.Unicode)]
    public static extern Int32 sd_notify(Int32 unsetEnvironment, String state);
}

public class InstallationDetails
{
    public InstallationDetails(String? ip, IReadOnlyList<Detail> details)
    {
        Details = details;
        Ip = ip;
    }

    public IReadOnlyList<Detail>? Details { get; set; }
    public String? Ip { get; set; }
}

public static partial class Db
{
    public static Dictionary<Installation, InstallationDetails> InstallationsAndDetails;


    private const String DbPath = "./db.sqlite";

    private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath);

    public static TableQuery<Installation> Installations => Connection.Table<Installation>();

    public static void Init()
    {
        // used to force static constructor
    }


    static Db()
    {
        // on startup create/migrate tables

        Connection.RunInTransaction(() => { Connection.CreateTable<Installation>(); });
    }
    
    public static async Task UpdateDetailsAndInstallations()
    {
        sd_notify(0, "READY=1");
        do {
            await UpdateInstallationsAndDetailsFromVrm(0);
        }
        while (true) ;
    }

    [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
    private static async Task UpdateInstallationsAndDetailsFromVrm(Int32 _)
    {
        var user = await GetVrmAccount();
        var readOnlyInstallations = await user.GetInstallations();
        
        var installations = readOnlyInstallations.ToList();
        installations.Shuffle();        // This

        foreach (var installation in installations)
        {
            Console.WriteLine(installation.Name);
             sd_notify(0, "WATCHDOG=1");
            var details = await GetInstallationDetails(installation);
            // Thread.Sleep(1000);
            await UpdateAlarms(installation);
            var ip = Ip(details);
            var updatedInstallation = new Installation(
                installation.Name,
                ip[0],
                (Int64)installation.IdSite,
                installation.Identifier,
                details.Details.MachineSerial() ?? "Unknown",
                HttpUtility.UrlEncode(installation.Name),
                ip[1],
                details.Details.Last().Json["timestamp"].ToString(),
                Serialize(details.Details),
                await NumberOfBatteries(ip[0], ip[1]),
                await BatteryFirmwareVersion(ip[0], ip[1]),
                "No updates"
            );
            
            if (ip[0] != "Unknown")
                await UpdateInstallationName(installation, ip[0]);
            
            if (GetInstallationByIdentifier(installation.Identifier) == null)
            {
                Create(updatedInstallation);
            }
            else
            {
                Update(updatedInstallation);
            }
        }
    }

    [RequiresUnreferencedCode(
        "Calls System.Text.Json.JsonSerializer.SerializeToElement<TValue>(TValue, JsonSerializerOptions)")]
    public static async Task<Boolean> UpdateAlarms(VrmInstallation installation)
    {
        // installation.GetDevices().batteryMonitor.setTemperatureAlarms(245,250,315,313);
        var alarmJson = JsonNode.Parse(File.ReadAllText("./alarm.json"))!.AsObject();
        var tags = await installation.GetTags();
        if (tags.Contains("FM-AF09"))
        {
            // Console.WriteLine(installation.IdSite.ToString());
            // await installation.GetAlarms();
            return await installation.SetAlarms(alarmJson);
            
        }

        return true;
    }

    public static async Task<VrmAccount> GetVrmAccount()
    {
        var fileContent = await File.ReadAllTextAsync("./token.json");
        var acc = Deserialize<AccToken>(fileContent);
        var user = VrmAccount.Token(acc!.idUser, acc.token);
        return user;
    }

    private static async Task<String> BatteryFirmwareVersion(String? ip, String? online)
    {
        if (ip is null or "Unknown" || online == "❌") return "Unknown";
        var pathToBattery = await ExecuteBufferedAsyncCommandOnIp(ip, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText");
        
        if (pathToBattery.Split('"')[1].StartsWith("Error")) return "Unknown";

        
        var command = $"dbus-send --system --dest={pathToBattery.Split('"')[1]} --type=method_call --print-reply /FirmwareVersion com.victronenergy.BusItem.GetText";
        var returnString = await ExecuteBufferedAsyncCommandOnIp(ip, command);
        var returnStringShortened = returnString.Split('"')[1];
        return returnStringShortened.Length > 10 ? "Unknown" : returnStringShortened;
    }

    private static async Task<String> NumberOfBatteries(String? ip, String? online)
    {
        if (ip is null or "Unknown" || online == "❌") return "Failed";
        var pathToBattery = await ExecuteBufferedAsyncCommandOnIp(ip, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText");

        if (pathToBattery.Split('"')[1].StartsWith("Error")) return "Failed";
        
        var cmd = await ExecuteBufferedAsyncCommandOnIp(ip,$"dbus-send --system --dest={pathToBattery.Split('"')[1]} --type=method_call --print-reply /NbOfBatteries com.victronenergy.BusItem.GetText" );
        var cmdResult = cmd.Split('"')[1];
        return cmdResult;  //No Batteries can be found
    }
    
    
    public static async Task<String> ExecuteBufferedAsyncCommandOnIp(String? ip, String command)
    {
        if (ip is null or "Unknown") return "Failed";
        
        var cmd = await Cli.Wrap("ssh")
            .WithArguments($@"root@{ip}")
            .AppendArgument("-o StrictHostKeyChecking=no")
            .AppendArgument(command)
            .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();
        return  cmd.ExitCode == 0 ? cmd.StandardOutput : cmd.StandardError;
    }

    private static String?[] Ip(InstallationDetails details)
    {
        var online = "❌";
        var lookup = details.Ip;
        if (lookup == "Unknown")
        {
            var serial = details.Details.MachineSerial() ?? "Unknown";
            
            if (serial != "Unknown" && FILE.Exists($@"/etc/openvpn/server/Salino/ccd/{serial}"))
                lookup = FILE.ReadAllText($@"/etc/openvpn/server/Salino/ccd/{serial}").Split(' ')[1];
        }
        else
        {
            online = "✅";
        }

        return new[] { lookup, online };
    }

    private static async Task<InstallationDetails> GetInstallationDetails(VrmInstallation i)
    {
        await Task.Delay(1000);
        try
        {
            var details = await i.GetDetails();

            var ip = await VpnInfo.LookUpIp(i.Identifier, details.MachineSerial()) ?? "Unknown";

            var installationDetails = new InstallationDetails(ip, details);

            return installationDetails;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        return new InstallationDetails("Unknown", Array.Empty<Detail>());
    }

    private static async Task UpdateInstallationName(VrmInstallation installation, String? ip)
    {
        var oldNameInFileRequest = await Cli.Wrap("ssh")
            .WithArguments($@"root@{ip}")
            .AppendArgument("-o StrictHostKeyChecking=accept-new")
            .AppendArgument("cat /data/innovenergy/openvpn/installation-name")
            .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();

        var oldNameInFileWithoutNewLine = oldNameInFileRequest.StandardOutput.TrimEnd();

        if (oldNameInFileRequest.ExitCode == 0 && oldNameInFileWithoutNewLine != installation.Name)
        {
            var overwriteNameCommand = Cli.Wrap("ssh")
                .WithArguments($@"root@{ip}")
                .AppendArgument($"echo '{installation.Name}' > /data/innovenergy/openvpn/installation-name");

            var overwriteNameResponse = await overwriteNameCommand
                .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();

            if (overwriteNameResponse.ExitCode != 0)
            {
                Console.WriteLine(overwriteNameResponse.StandardError);
                Console.WriteLine("Renaming did not work");
            }
            else
            {
                var rebootAfterRename = await Cli.Wrap("ssh")
                    .WithArguments($@"root@{ip}")
                    .AppendArgument("reboot")
                    .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();

                if (rebootAfterRename.ExitCode != 0)
                {
                    Console.WriteLine(overwriteNameResponse.StandardError);
                    Console.WriteLine("Rebooting did not work");
                }
                else
                {
                    Console.WriteLine($"Renamed and rebooted {installation.Name}");
                }
            }
        }
    }


    private static Boolean RunTransaction(Func<Boolean> func)
    {
        var savepoint = Connection.SaveTransactionPoint();
        var success = false;

        try
        {
            success = func();
        }
        finally
        {
            if (success)
                Connection.Release(savepoint);
            else
                Connection.RollbackTo(savepoint);
        }

        return success;
    }
}

public class AccToken
{
    public UInt64 idUser { get; init;} 
    public String token { get; init;}
}


/*
dbus-send --system --dest=com.victronenergy.battery.ttyUSB1 --print-reply /FirmwareVersion \
org.freedesktop.DBus.Properties.Get string:com.victronenergy.battery.ttyUSB1
 */