using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Hua.Todo.Application.Interfaces;
using Hua.Todo.Application.Models;
using Hua.Todo.Maui.Models;

namespace Hua.Todo.Maui.Services;

/// <summary>
/// Android 平台嵌入式 Web 服务器服务。
/// 使用 TcpListener 自定义实现简单的 HTTP 服务器，用于离线模式下托管 API 和静态资源。
/// </summary>
public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService
{
	private readonly AppSettings _appSettings;
	private readonly IServiceProvider _services;

	private TcpListener? _listener;
	private CancellationTokenSource? _cts;
	private Task? _acceptLoop;

	/// <summary>
	/// 服务器是否正在运行。
	/// </summary>
	public bool IsRunning => _listener != null;

	/// <summary>
	/// 服务器基础 URL。
	/// </summary>
	public string BaseUrl => $"http://localhost:{_appSettings.WebServer.Port}";

	/// <summary>
	/// 初始化 <see cref="MobileEmbeddedWebServerService"/>。
	/// </summary>
	/// <param name="appSettings">应用程序配置。</param>
	/// <param name="services">依赖注入服务提供者。</param>
	public MobileEmbeddedWebServerService(AppSettings appSettings, IServiceProvider services)
	{
		_appSettings = appSettings;
		_services = services;
	}

	/// <summary>
	/// 异步启动服务器。
	/// 启动时机：在 Android 平台初始化或手动开启时调用。
	/// 错误处理：启动失败会记录日志，建议外部调用时关注 <see cref="IsRunning"/>。
	/// 线程安全：可在任何线程调用；内部通过状态检查避免重复启动。
	/// </summary>
	/// <returns>表示启动操作的任务。</returns>
	public Task StartAsync()
	{
		if (_listener != null) return Task.CompletedTask;

		_cts = new CancellationTokenSource();
		_listener = new TcpListener(IPAddress.Loopback, _appSettings.WebServer.Port);
		_listener.Start();

		_acceptLoop = Task.Run(() => AcceptLoopAsync(_cts.Token));
		return Task.CompletedTask;
	}

	/// <summary>
	/// 异步停止服务器。
	/// 释放所有资源并停止监听。
	/// </summary>
	/// <returns>表示停止操作的任务。</returns>
	public async Task StopAsync()
	{
		if (_listener == null) return;

		try
		{
			_cts?.Cancel();
		}
		catch
		{
		}

		try
		{
			_listener.Stop();
		}
		catch
		{
		}

		try
		{
			if (_acceptLoop != null) await _acceptLoop;
		}
		catch
		{
		}

		_listener = null;
		_acceptLoop = null;
		_cts?.Dispose();
		_cts = null;
	}

	private async Task AcceptLoopAsync(CancellationToken token)
	{
		while (!token.IsCancellationRequested && _listener != null)
		{
			TcpClient? client = null;
			try
			{
				client = await _listener.AcceptTcpClientAsync(token);
				_ = Task.Run(() => HandleClientAsync(client, token), token);
			}
			catch
			{
				client?.Dispose();
			}
		}
	}

	private async Task HandleClientAsync(TcpClient client, CancellationToken token)
	{
		using var _ = client;
		using var stream = client.GetStream();

		HttpRequestData request;
		try
		{
			request = await ReadRequestAsync(stream, token);
		}
		catch
		{
			return;
		}

		if (request.Path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
		{
			await HandleApiAsync(stream, request, token);
			return;
		}

		await HandleStaticAsync(stream, request, token);
	}

	private async Task HandleApiAsync(Stream stream, HttpRequestData request, CancellationToken token)
	{
		try
		{
			var path = request.Path;
			if (!path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
			{
				await WriteJsonAsync(stream, 404, new { Success = false, Data = (object?)null, Message = "请求不存在", Errors = new[] { "Not Found" } }, token);
				return;
			}

			var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
			if (segments.Length < 2)
			{
				await WriteJsonAsync(stream, 404, new { Success = false, Data = (object?)null, Message = "请求不存在", Errors = new[] { "Not Found" } }, token);
				return;
			}

			var serviceName = segments[1];
			if (!serviceName.Equals("task", StringComparison.OrdinalIgnoreCase))
			{
				await WriteJsonAsync(stream, 404, new { Success = false, Data = (object?)null, Message = "请求不存在", Errors = new[] { "Not Found" } }, token);
				return;
			}

			using var scope = _services.CreateScope();
			var taskService = scope.ServiceProvider.GetRequiredService<ITaskService>();

			object? result = null;
			var methodName = string.Empty;

			var tail = segments.Skip(2).ToArray();

			if (request.Method == "GET" && tail.Length == 0)
			{
				methodName = nameof(ITaskService.GetAllTasksAsync);
				result = await taskService.GetAllTasksAsync();
			}
			else if (request.Method == "GET" && tail.Length == 1 && tail[0].Equals("active", StringComparison.OrdinalIgnoreCase))
			{
				methodName = nameof(ITaskService.GetActiveTasksAsync);
				result = await taskService.GetActiveTasksAsync();
			}
			else if (request.Method == "GET" && tail.Length == 1 && tail[0].Equals("completed", StringComparison.OrdinalIgnoreCase))
			{
				methodName = nameof(ITaskService.GetCompletedTasksAsync);
				result = await taskService.GetCompletedTasksAsync();
			}
			else if (request.Method == "GET" && tail.Length == 1 && int.TryParse(tail[0], out var getId))
			{
				methodName = nameof(ITaskService.GetTaskByIdAsync);
				result = await taskService.GetTaskByIdAsync(getId);
			}
			else if (request.Method == "GET" && tail.Length == 2 && tail[1].Equals("subtasks", StringComparison.OrdinalIgnoreCase) && int.TryParse(tail[0], out var parentId))
			{
				methodName = nameof(ITaskService.GetSubTasksAsync);
				result = await taskService.GetSubTasksAsync(parentId);
			}
			else if (request.Method == "POST" && tail.Length == 0)
			{
				methodName = nameof(ITaskService.CreateTaskAsync);
				var dto = DeserializeBody<CreateTaskDto>(request.Body);
				result = await taskService.CreateTaskAsync(dto);
			}
			else if (request.Method == "PUT" && tail.Length == 0)
			{
				methodName = nameof(ITaskService.UpdateTaskAsync);
				var dto = DeserializeBody<UpdateTaskDto>(request.Body);
				result = await taskService.UpdateTaskAsync(dto);
			}
			else if (request.Method == "PATCH" && tail.Length == 2 && tail[1].Equals("toggle", StringComparison.OrdinalIgnoreCase) && int.TryParse(tail[0], out var toggleId))
			{
				methodName = nameof(ITaskService.ToggleCompleteAsync);
				result = await taskService.ToggleCompleteAsync(toggleId);
			}
			else if (request.Method == "DELETE" && tail.Length == 1 && int.TryParse(tail[0], out var deleteId))
			{
				methodName = nameof(ITaskService.DeleteTaskAsync);
				await taskService.DeleteTaskAsync(deleteId);
				result = null;
			}
			else
			{
				await WriteJsonAsync(stream, 404, new { Success = false, Data = (object?)null, Message = "请求不存在", Errors = new[] { "Not Found" } }, token);
				return;
			}

			var response = CreateApiResponse(result, null, methodName);
			await WriteJsonAsync(stream, response.StatusCode, response.Payload, token);
		}
		catch (Exception ex)
		{
			var response = CreateApiResponse(null, ex, string.Empty);
			await WriteJsonAsync(stream, response.StatusCode, response.Payload, token);
		}
	}

	private async Task HandleStaticAsync(Stream stream, HttpRequestData request, CancellationToken token)
	{
		var path = request.Path;
		if (string.IsNullOrEmpty(path) || path == "/") path = "/index.html";

		if (path.StartsWith("/api", StringComparison.OrdinalIgnoreCase))
		{
			await WriteTextAsync(stream, 404, "Not Found", "text/plain; charset=utf-8", token);
			return;
		}

		var assetPath = $"wwwroot{path}";
		if (path.Contains("..", StringComparison.Ordinal))
		{
			await WriteTextAsync(stream, 400, "Bad Request", "text/plain; charset=utf-8", token);
			return;
		}

		if (!TryOpenAsset(assetPath, out var assetStream))
		{
			if (!Path.HasExtension(path))
			{
				assetPath = "wwwroot/index.html";
				if (TryOpenAsset(assetPath, out assetStream))
				{
					await using (assetStream)
					{
						await WriteStreamAsync(stream, 200, assetStream, "text/html; charset=utf-8", token);
					}
					return;
				}
			}

			await WriteTextAsync(stream, 404, "Not Found", "text/plain; charset=utf-8", token);
			return;
		}

		await using (assetStream)
		{
			await WriteStreamAsync(stream, 200, assetStream, GetContentType(path), token);
		}
	}

	private static bool TryOpenAsset(string assetPath, out Stream stream)
	{
		try
		{
			stream = global::Android.App.Application.Context.Assets.Open(assetPath);
			return true;
		}
		catch
		{
			stream = Stream.Null;
			return false;
		}
	}

	private static string GetContentType(string path)
	{
		var ext = Path.GetExtension(path).ToLowerInvariant();
		return ext switch
		{
			".html" => "text/html; charset=utf-8",
			".js" => "text/javascript; charset=utf-8",
			".css" => "text/css; charset=utf-8",
			".svg" => "image/svg+xml",
			".png" => "image/png",
			".jpg" or ".jpeg" => "image/jpeg",
			".gif" => "image/gif",
			".ico" => "image/x-icon",
			".json" => "application/json; charset=utf-8",
			".map" => "application/json; charset=utf-8",
			".txt" => "text/plain; charset=utf-8",
			_ => "application/octet-stream"
		};
	}

	private static (int StatusCode, object Payload) CreateApiResponse(object? result, Exception? exception, string methodName)
	{
		var successMessage = methodName switch
		{
			_ when methodName.StartsWith("Get", StringComparison.OrdinalIgnoreCase) => "获取成功",
			_ when methodName.StartsWith("Create", StringComparison.OrdinalIgnoreCase) => "创建成功",
			_ when methodName.StartsWith("Update", StringComparison.OrdinalIgnoreCase) => "更新成功",
			_ when methodName.StartsWith("Delete", StringComparison.OrdinalIgnoreCase) => "删除成功",
			_ when methodName.StartsWith("Toggle", StringComparison.OrdinalIgnoreCase) => "操作成功",
			_ => "操作成功"
		};

		var failureMessage = methodName switch
		{
			_ when methodName.StartsWith("Get", StringComparison.OrdinalIgnoreCase) => "获取失败",
			_ when methodName.StartsWith("Create", StringComparison.OrdinalIgnoreCase) => "创建失败",
			_ when methodName.StartsWith("Update", StringComparison.OrdinalIgnoreCase) => "更新失败",
			_ when methodName.StartsWith("Delete", StringComparison.OrdinalIgnoreCase) => "删除失败",
			_ when methodName.StartsWith("Toggle", StringComparison.OrdinalIgnoreCase) => "操作失败",
			_ => "操作失败"
		};

		List<string>? errors = null;
		if (exception != null)
		{
			errors = new List<string> { exception.Message };
		}

		var payload = new
		{
			Success = exception == null,
			Data = exception == null ? result : null,
			Message = exception == null ? successMessage : failureMessage,
			Errors = errors
		};

		var statusCode = exception switch
		{
			KeyNotFoundException => 404,
			ArgumentException => 400,
			_ => 200
		};

		return (statusCode, payload);
	}

	private static readonly JsonSerializerOptions JsonOptions = new()
	{
		PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
		Converters = { new JsonStringEnumConverter() }
	};

	private static T DeserializeBody<T>(string body) where T : new()
	{
		if (string.IsNullOrWhiteSpace(body)) return new T();
		return JsonSerializer.Deserialize<T>(body, new JsonSerializerOptions
		{
			PropertyNameCaseInsensitive = true,
			Converters = { new JsonStringEnumConverter() }
		}) ?? new T();
	}

	private static async Task WriteJsonAsync(Stream stream, int statusCode, object payload, CancellationToken token)
	{
		var json = JsonSerializer.Serialize(payload, JsonOptions);
		await WriteTextAsync(stream, statusCode, json, "application/json; charset=utf-8", token);
	}

	private static async Task WriteTextAsync(Stream stream, int statusCode, string body, string contentType, CancellationToken token)
	{
		var bytes = Encoding.UTF8.GetBytes(body);
		var header =
			$"HTTP/1.1 {statusCode} {GetReasonPhrase(statusCode)}\r\n" +
			$"Content-Type: {contentType}\r\n" +
			$"Content-Length: {bytes.Length}\r\n" +
			$"Connection: close\r\n" +
			$"\r\n";

		var headerBytes = Encoding.ASCII.GetBytes(header);
		await stream.WriteAsync(headerBytes, token);
		await stream.WriteAsync(bytes, token);
	}

	private static async Task WriteStreamAsync(Stream stream, int statusCode, Stream bodyStream, string contentType, CancellationToken token)
	{
		await using var ms = new MemoryStream();
		await bodyStream.CopyToAsync(ms, token);
		var bodyBytes = ms.ToArray();

		var header =
			$"HTTP/1.1 {statusCode} {GetReasonPhrase(statusCode)}\r\n" +
			$"Content-Type: {contentType}\r\n" +
			$"Content-Length: {bodyBytes.Length}\r\n" +
			$"Connection: close\r\n" +
			$"\r\n";

		var headerBytes = Encoding.ASCII.GetBytes(header);
		await stream.WriteAsync(headerBytes, token);
		await stream.WriteAsync(bodyBytes, token);
	}

	private static async Task<HttpRequestData> ReadRequestAsync(NetworkStream stream, CancellationToken token)
	{
		using var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, bufferSize: 8192, leaveOpen: true);
		var requestLine = await reader.ReadLineAsync(token);
		if (string.IsNullOrWhiteSpace(requestLine)) throw new InvalidOperationException("empty request line");

		var parts = requestLine.Split(' ');
		if (parts.Length < 2) throw new InvalidOperationException("invalid request line");

		var method = parts[0].Trim().ToUpperInvariant();
		var target = parts[1].Trim();

		string path;
		if (Uri.TryCreate(target, UriKind.Absolute, out var absoluteUri) &&
		    (absoluteUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ||
		     absoluteUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)))
		{
			path = absoluteUri.AbsolutePath;
		}
		else if (target.StartsWith("//", StringComparison.Ordinal) &&
		         Uri.TryCreate("http:" + target, UriKind.Absolute, out var authorityUri))
		{
			path = authorityUri.AbsolutePath;
		}
		else
		{
			path = target;
			var queryIndex = target.IndexOf('?', StringComparison.Ordinal);
			if (queryIndex >= 0) path = target.Substring(0, queryIndex);
		}

		if (string.IsNullOrEmpty(path)) path = "/";
		if (!path.StartsWith("/", StringComparison.Ordinal)) path = "/" + path;

		var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		string? line;
		while (!string.IsNullOrEmpty(line = await reader.ReadLineAsync(token)))
		{
			var idx = line.IndexOf(':');
			if (idx <= 0) continue;
			var name = line.Substring(0, idx).Trim();
			var value = line.Substring(idx + 1).Trim();
			headers[name] = value;
		}

		var body = string.Empty;
		if (headers.TryGetValue("Content-Length", out var contentLengthStr) && int.TryParse(contentLengthStr, out var contentLength) && contentLength > 0)
		{
			var buffer = new char[contentLength];
			var read = 0;
			while (read < contentLength)
			{
				var n = await reader.ReadAsync(buffer, read, contentLength - read);
				if (n <= 0) break;
				read += n;
			}
			body = new string(buffer, 0, read);
		}

		return new HttpRequestData(method, path, body);
	}

	private static string GetReasonPhrase(int statusCode) => statusCode switch
	{
		200 => "OK",
		400 => "Bad Request",
		404 => "Not Found",
		500 => "Internal Server Error",
		_ => "OK"
	};

	private readonly record struct HttpRequestData(string Method, string Path, string Body);
}
