// =============================================================================
// Tesla.Contract — Console-side RPC client  (net48 only)
// =============================================================================
// Opens an OFB-encrypted TCP connection to the pod (port 53290) and dispatches
// ILauncherService calls as framed JSON RpcRequest / RpcResponse pairs (see
// PodRpcProtocol.cs). Replaces the original serialized-MethodBase + BinaryFormatter
// scheme; the OFB transport (PodConfigurationServer.NegotiateCryptoStreams) is
// unchanged.
//
// Depends on Tesla.PodConfigurationServer (Tesla.SecureConfig) for the crypto
// handshake, so it is compiled for net48 only. The Launcher is the server end.
// =============================================================================

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text.Json;
using System.Threading;

namespace Tesla.Net
{
    public class PodManagerConnection : IPodManagerConnection, ILauncherService, IDisposable
    {
        private TcpClient mOpenClient;

        private object mCommSyncRoot = new object();

        protected Stream mUnderlyingOutStream;

        private BufferedStream mReader;

        private BufferedStream mWriter;

        private IPEndPoint mEndPoint;

        private byte[] mKey;

        public bool IsOpen
        {
            get
            {
                if (mOpenClient == null || mOpenClient.Client == null || mWriter == null || mReader == null)
                {
                    return false;
                }
                return mOpenClient.Connected;
            }
        }

        protected object SyncRoot => mCommSyncRoot;

        public float VolumeLevel
        {
            get => Invoke<float>("get_VolumeLevel");
            set => InvokeVoid("set_VolumeLevel", value);
        }

        public void Open(IPEndPoint remoteEndPoint, byte[] secureKey)
        {
            mEndPoint = remoteEndPoint;
            mKey = secureKey;
            mOpenClient = new TcpClient();
            mOpenClient.SendTimeout = 10000;
            mOpenClient.ReceiveTimeout = 10000;
            mOpenClient.Connect(remoteEndPoint);
            if (!PodConfigurationServer.NegotiateCryptoStreams(mOpenClient.GetStream(), secureKey, out var outStream, out var inStream))
            {
                mOpenClient.Close();
                throw new IOException("Error connecting to pod.");
            }
            mUnderlyingOutStream = outStream;
            mWriter = new BufferedStream(outStream);
            mReader = new BufferedStream(inStream);
        }

        public void Close()
        {
            try
            {
                mWriter.Close();
                mReader.Close();
                mOpenClient.Client.Close();
                mOpenClient = null;
            }
            catch
            {
            }
        }

        public Guid InstallProduct(string filepath)
        {
            if (!File.Exists(filepath))
            {
                return Guid.Empty;
            }
            FileStream outOfBandData = File.OpenRead(filepath);
            Guid result = Guid.Empty;
            try
            {
                result = OutOfBandInvocation.Invoke(mEndPoint, mKey, "InitiateInstallProduct", new object[0], outOfBandData);
                return result;
            }
            catch (IOException)
            {
                return result;
            }
        }

        // ── RPC core ─────────────────────────────────────────────────────────
        // Writes a framed JSON request and (optionally) reads the framed response,
        // deserializing its Result into the caller's expected type.

        protected object InvokeCore(string method, bool waitForResponse, Type resultType, object[] parameters)
        {
            try
            {
                lock (mCommSyncRoot)
                {
                    PodRpc.WriteRequest(mWriter, method, parameters);
                    if (!waitForResponse)
                    {
                        return null;
                    }
                    RpcResponse response = PodRpc.ReadResponse(mReader);
                    if (response.Error != null)
                    {
                        throw new Exception("Server function threw an exception: " + response.Error);
                    }
                    if (resultType == null
                        || response.Result.ValueKind == JsonValueKind.Null
                        || response.Result.ValueKind == JsonValueKind.Undefined)
                    {
                        return resultType != null && resultType.IsValueType
                            ? Activator.CreateInstance(resultType)
                            : null;
                    }
                    return response.Result.Deserialize(resultType, PodRpc.JsonOptions);
                }
            }
            catch (IOException innerException)
            {
                throw new TeslaConsole.ConnectionLostException("Connection Lost", innerException);
            }
        }

        protected T Invoke<T>(string method, params object[] parameters)
            => (T)InvokeCore(method, waitForResponse: true, typeof(T), parameters);

        protected void InvokeVoid(string method, params object[] parameters)
            => InvokeCore(method, waitForResponse: true, null, parameters);

        protected void InvokeOneWay(string method, params object[] parameters)
            => InvokeCore(method, waitForResponse: false, null, parameters);

        // ── ILauncherService ─────────────────────────────────────────────────

        public void ClearStore() => InvokeOneWay("ClearStore");

        public DateTime Ping(DateTime now) => Invoke<DateTime>("Ping", now);

        public int LaunchApp(Guid launchKey) => Invoke<int>("LaunchApp", launchKey);

        public LaunchPair[] GetLaunchableApps() => Invoke<LaunchPair[]>("GetLaunchableApps");

        public Guid InitiateInstallProduct() => Invoke<Guid>("InitiateInstallProduct");

        public OutOfBandProgress GetOutOfBandProgress(Guid installId)
            => Invoke<OutOfBandProgress>("GetOutOfBandProgress", installId);

        public void KillApp(Guid launchKey, int processId)
            => InvokeVoid("KillApp", launchKey, processId);

        public void KillAllOfType(Guid launchKey) => InvokeVoid("KillAllOfType", launchKey);

        public void KillAllApps() => InvokeVoid("KillAllApps");

        public void Shutdown(bool doRestart) => InvokeVoid("Shutdown", doRestart);

        public LaunchedAppData[] GetLaunchedApps() => Invoke<LaunchedAppData[]>("GetLaunchedApps");

        public LaunchData[] GetInstalledApps() => Invoke<LaunchData[]>("GetInstalledApps");

        public void RemoveApp(Guid index) => InvokeVoid("RemoveApp", index);

        public void InstallApp(LaunchData data) => InvokeVoid("InstallApp", data);

        public void UninstallApp(Guid launchKey) => InvokeVoid("UninstallApp", launchKey);

        public FullUpdateData FullUpdate() => Invoke<FullUpdateData>("FullUpdate");

        public void Dispose()
        {
            Close();
        }
    }

    public class OutOfBandInvocation : PodManagerConnection
    {
        private Stream mOutOfBandData;

        public static Guid Invoke(IPEndPoint target, byte[] key, string methodName, object[] parameters, Stream outOfBandData)
        {
            return Invoke(target, key, methodName, parameters, outOfBandData, null);
        }

        public static Guid Invoke(IPEndPoint target, byte[] key, MethodBase method, object[] parameters, Stream outofBandData)
        {
            return Invoke(target, key, method.Name, parameters, outofBandData, null);
        }

        public static Guid Invoke(IPEndPoint target, byte[] key, MethodBase method, object[] parameters, Stream outOfBandData, OutOfBandProgressChanged progressCallback)
        {
            return Invoke(target, key, method.Name, parameters, outOfBandData, progressCallback);
        }

        public static Guid Invoke(IPEndPoint target, byte[] key, string methodName, object[] parameters, Stream outOfBandData, OutOfBandProgressChanged progressCallback)
        {
            OutOfBandInvocation outOfBandInvocation = new OutOfBandInvocation(target, key);
            // progressCallback is vestigial: install progress is polled via
            // GetOutOfBandProgress, never pushed. Kept on the signature for surface parity.
            _ = progressCallback;
            if (outOfBandData != null)
            {
                outOfBandInvocation.mOutOfBandData = outOfBandData;
            }
            Guid result = outOfBandInvocation.Invoke<Guid>(methodName, parameters ?? new object[0]);
            ThreadPool.QueueUserWorkItem(AppendOutOfBandData, outOfBandInvocation);
            return result;
        }

        protected static void AppendOutOfBandData(object state)
        {
            OutOfBandInvocation outOfBandInvocation = (OutOfBandInvocation)state;
            using (outOfBandInvocation)
            {
                try
                {
                    outOfBandInvocation.WriteOutOfBandData();
                }
                catch (IOException)
                {
                }
            }
        }

        protected OutOfBandInvocation(IPEndPoint target, byte[] key)
        {
            Open(target, key);
        }

        protected void WriteOutOfBandData()
        {
            lock (base.SyncRoot)
            {
                int num = 8192;
                byte[] buffer = new byte[num];
                long num2 = mOutOfBandData.Length;
                mUnderlyingOutStream.Write(BitConverter.GetBytes(num2), 0, 8);
                while (num2 > num)
                {
                    mOutOfBandData.Read(buffer, 0, num);
                    mUnderlyingOutStream.Write(buffer, 0, num);
                    num2 -= num;
                }
                if (num2 > 0)
                {
                    mOutOfBandData.Read(buffer, 0, (int)num2);
                    mUnderlyingOutStream.Write(buffer, 0, (int)num2);
                }
                mUnderlyingOutStream.Flush();
            }
        }
    }
}
