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

using Forwarder.Domain;
using Forwarder.Exceptions;
using Forwarder.Models;
using Forwarder.Repositories;
using Forwarder.Services.Implementations.NetworkSenders;

using Microsoft.Extensions.Options;

using Serilog;

namespace Forwarder.Services;

/// <summary>
/// Слушатель TCP-соединений.
/// </summary>
/// <param name="options">Опции.</param>
/// <param name="serviceProvider">Сервис-провайдер.</param>
internal class TcpWatcher(
	IOptions<ForwarderOptions> options,
	IServiceProvider serviceProvider
) : IHostedService
{
	private bool _enabled;
	private Task? _task;

	/// <inheritdoc />
	public Task StartAsync(CancellationToken cancellationToken)
	{
		_enabled = true;
		_task = Task.Run(() => Listening(cancellationToken), cancellationToken);
		return Task.CompletedTask;
	}

	private async Task Listening(CancellationToken cancellationToken)
	{
		while (_enabled)
		{
			try
			{
				await Start(cancellationToken);
			}
			catch (Exception ex)
			{
				Log.Error(ex, "Error listening");
			}
			finally
			{
				await Log.CloseAndFlushAsync();
			}
		}
	}

	private async Task Start(CancellationToken cancellationToken)
	{
		using var tcpListener = new TcpListener(IPAddress.Any, options.Value.TcpPort);
		tcpListener.Start();
		Log.Information("TCP-server is running. Port: {Port}", options.Value.TcpPort);

		while (_enabled)
		{
			try
			{
				Log.Information("Ожидание соединения...");

				var client = await tcpListener.AcceptTcpClientAsync(cancellationToken);
				_ = HandleClientAsync(client, cancellationToken);
			}
			catch (Exception ex)
			{
				Log.Error(ex, "Ошибка при обработке клиента");
				throw new NeedRecreateException();
			}
		}
	}

	private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
	{
		Log.Information("Соединение с клиентом установлено.");

		using (client)
		{
			using var stream = client.GetStream();

			try
			{
				while (true)
				{
					var messageResult = await ReadData(stream, cancellationToken);
					if (messageResult.IsFail)
					{
						break;
					}

					var messageConverterService = serviceProvider
						.CreateScope()
						.ServiceProvider
						.GetRequiredService<IMessageConverterService>();

					Log.Information("Получено сообщение по TCP: {Message}",
						messageConverterService.FormatMessage(messageResult.Value));

					var converter = messageConverterService.FindMessageConverter(messageResult.Value);
					if (converter.IsFail)
					{
						Log.Error(converter.Error);
						break;
					}

					Log.Information("Найден конвертер сообщения: {Converter}", converter.Value.Name);

					var message = converter.Value.Parse(messageResult.Value);
					if (message.IsFail)
					{
						Log.Error(message.Error);
						break;
					}

					if (string.IsNullOrWhiteSpace(message.Value.Abonent))
					{
						Log.Information("Обнаружен пустой абонент. Пинг.");
						break;
					}

					await WriteMessage(message.Value, cancellationToken);

					_ = await converter.Value.SendAnswer(
						new TcpNetworkSender(stream),
						cancellationToken
					);

					Log.Information("Сообщение обработано.");
				}
			}
			catch (Exception ex)
			{
				Log.Error(ex, "Ошибка при обработке клиента");
			}
			finally
			{
				stream.Close();
				client.Close();
				Log.Information("Соединение с клиентом закрыто.");
			}
		}
	}

	private static async Task<Result<string>> ReadData(NetworkStream stream, CancellationToken cancellationToken)
	{
		var buffer = new Memory<byte>(new byte[1024]);

		var bytesRead = await stream.ReadAsync(buffer, cancellationToken);
		if (bytesRead == 0)
		{
			Log.Information("Клиент отключился.");
			return Result<string>.Fail("Клиент отключился.");
		}

		var message = Encoding.ASCII.GetString(buffer.Span);
		return Result<string>.Success(message);
	}

	private async Task WriteMessage(Message message, CancellationToken cancellationToken)
	{
		Log.Information("TCP-сообщение: {Message}", message);

		var messageRepository = serviceProvider
			.CreateScope()
			.ServiceProvider
			.GetRequiredService<IMessageRepository>();

		_ = await messageRepository.AddAsync(message, cancellationToken);
	}

	/// <inheritdoc />
	public Task StopAsync(CancellationToken cancellationToken)
	{
		_enabled = false;
		return _task ?? Task.CompletedTask;
	}
}
