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>
/// Слушатель UCP-соединений.
/// </summary>
/// <param name="options">Опции.</param>
/// <param name="serviceProvider">Сервис-провайдер.</param>
internal class UdpWatcher(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)
	{
		Log.Information("UDP-server is running. Port: {Port}", options.Value.UdpPort);
		using var udpClient = new UdpClient(options.Value.UdpPort);

		while (_enabled)
		{
			try
			{
				_ = await HandleClientAsync(udpClient, cancellationToken);
			}
			catch (Exception ex)
			{
				Log.Information("Ошибка при обработке сообщения: {Error}", ex.Message);
				Log.Error(ex, "Ошибка при обработке сообщения.");
				throw new NeedRecreateException();
			}
		}
	}

	private async Task<Result> HandleClientAsync(
		UdpClient udpClient,
		CancellationToken cancellationToken
	)
	{
		var receiveResult = await udpClient.ReceiveAsync(cancellationToken);
		var messageText = Encoding.ASCII.GetString(receiveResult.Buffer);

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

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

		var converter = messageConverterService.FindMessageConverter(messageText);
		if (converter.IsFail)
		{
			Log.Error(converter.Error);
			return converter.Error;
		}

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

		var message = converter.Value.Parse(messageText);
		Log.Information(
			"Результат распознавания: {Message}",
			message.IsFail ? message.Error : message.Value.ToString()
		);

		if (message.IsFail)
		{
			Log.Error(message.Error);
			return message.Error;
		}

		Log.Information("Сообщение распознано.");

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

		await WriteMessage(message.Value, cancellationToken);

		_ = await converter.Value.SendAnswer(
			new UdpNetworkSender(udpClient, receiveResult.RemoteEndPoint),
			cancellationToken
		);

		Log.Information("Сообщение обработано.");

		return Result.Success();
	}

	private async Task WriteMessage(Message message, CancellationToken cancellationToken)
	{
		Log.Information("UDP-сообщение: {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;
	}
}
