using System.Net.Sockets;
using System.Text;

namespace InnovEnergy.Lib.Protocols.DBus.Transport;

public class AuthenticationMethod
{
    public static AuthenticationMethod Anonymous = new AuthenticationMethod(AuthenticateAnonymous);

    public static AuthenticationMethod External(UInt16 userId) => new AuthenticationMethod(s => AuthenticateExternal(s, userId));
    public static AuthenticationMethod External()              => new AuthenticationMethod(s => AuthenticateExternal(s, Env.UserId));
    public static AuthenticationMethod ExternalAsRoot()        => new AuthenticationMethod(s => AuthenticateExternal(s, 0));

    // TODO: other auth modes

    public Func<Socket, Guid> Authenticate { get; }

    private AuthenticationMethod(Func<Socket, Guid> authenticate)
    {
        Authenticate = authenticate;
    }

    private static Guid AuthenticateExternal(Socket socket, UInt16 userId)
    {
        var user = String
                  .Concat(Encoding.ASCII.GetBytes(userId.ToString())
                  .Select(c => c.ToString("X2")));

        return AuthenticateAsClient(socket, $"AUTH EXTERNAL {user}");
    }

    private static Guid AuthenticateAnonymous(Socket s)
    {
        return AuthenticateAsClient(s, "AUTH ANONYMOUS");
    }

    private static Guid AuthenticateAsClient(Socket socket, String authCommand)
    {
        socket.Send(new Byte[] { 0 }, SocketFlags.None);
        SendLine(authCommand);

        var reply = ReceiveLine();

        if (reply is null)
            throw new Exception("Connection failure. Connection closed.");

        var args = reply.Trim().Split(' ');

        if (args[0] != "OK")
            throw new Exception("Authentication failure");

        SendLine("BEGIN");

        return args[1] != String.Empty
             ? Guid.ParseExact(args[1], "N")
             : Guid.Empty;


        String? ReceiveLine()
        {
            using var stream = new NetworkStream(socket);
            using var reader = new StreamReader(stream, Encoding.ASCII);
            return reader.ReadLine();
        }

        void SendLine(String line)
        {
            using var stream = new NetworkStream(socket);
            using var writer = new StreamWriter(stream, Encoding.ASCII) { NewLine = "\r\n" };
            writer.WriteLine(line);
        }
    }

}