using Forwarder.Data;
using Forwarder.Domain;
using Forwarder.Models;

using Microsoft.EntityFrameworkCore;

using Serilog;

namespace Forwarder.Repositories.Implementations;

/// <inheritdoc />
public class AssetMapRepository(
	IDataContext dataContext
) : IAssetMapRepository
{
	private const string NotFoundMessage = "Маппинг не найден";
	private const string AlreadyExistsMessage = "Маппинг с таким ИД объекта уже существует";

	/// <inheritdoc />
	public async Task<Result<AssetMap>> AddAsync(
		string abonent,
		Guid? assetId = null,
		CancellationToken cancellationToken = default)
	{
		try
		{
			var map = await dataContext.Maps
				.FirstOrDefaultAsync(x => x.Abonent == abonent, cancellationToken);
			if (map is not null)
			{
				return $"Абонент уже существует в маппинге. ИД маппнига: {map.Id}";
			}

			if (assetId.HasValue)
			{
				map = await dataContext.Maps
					.FirstOrDefaultAsync(x => x.AssetId == assetId.Value, cancellationToken);
				if (map is not null)
				{
					return $"ИД объекта уже существует в маппинге. ИД маппнига: {map.Id}";
				}
			}

			map = new AssetMap()
			{
				Abonent = abonent,
				AssetId = assetId,
			};

			_ = await dataContext.Maps.AddAsync(map, cancellationToken);
			_ = await dataContext.SaveChangesAsync(cancellationToken);

			return map;
		}
		catch (Exception ex)
		{
			return ex.Message;
		}
	}

	public async Task<Result> DeleteAsync(Guid id, CancellationToken cancellationToken)
	{
		try
		{
			var assetMap = await dataContext.Maps
				.AsNoTracking()
				.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
			if (assetMap is null)
			{
				return Result.Fail("Маппинг не найден");
			}

			dataContext.Maps.Remove(assetMap);
			_ = await dataContext.SaveChangesAsync(cancellationToken);

			return Result.Success();
		}
		catch (Exception ex)
		{
			return Result.Fail(ex.Message);
		}
	}

	/// <inheritdoc />
	public async Task<Result<AssetMap>> Get(Guid id, CancellationToken cancellationToken)
	{
		var assetMap = await dataContext.Maps
			.AsNoTracking()
			.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
		if (assetMap is null)
		{
			return Result<AssetMap>.Fail("Маппинг не найден");
		}

		return Result<AssetMap>.Success(assetMap);
	}

	/// <inheritdoc />
	public IQueryable<AssetMap> GetQuery()
	{
		return dataContext.Maps
			.AsNoTracking();
	}

	/// <inheritdoc />
	public async Task<Result<List<AssetMap>>> GetRequiredMapsAndCreateIsNewAsync(IEnumerable<string> abonentList, CancellationToken cancellationToken)
	{
		try
		{
			var maps = await dataContext.Maps
				.Where(x => abonentList.Contains(x.Abonent))
				.ToListAsync(cancellationToken);

			var mapsWithAssetIds = maps.Where(x => x.AssetId.HasValue);
			if (mapsWithAssetIds.Count() == abonentList.Count())
			{
				return Result<List<AssetMap>>.Success(maps);
			}

			var newMaps = abonentList
				.Where(x => !maps.Exists(y => y.Abonent == x))
				.Select(x => new AssetMap
				{
					Abonent = x
				})
				.ToList();

			await dataContext.Maps.AddRangeAsync(newMaps, cancellationToken);
			_ = await dataContext.SaveChangesAsync(cancellationToken);

			return Result<List<AssetMap>>.Success([.. maps, .. newMaps]);
		}
		catch (Exception ex)
		{
			return Result<List<AssetMap>>.Fail(ex.Message);
		}
	}

	/// <inheritdoc />
	public async Task<Result<ITableStateResponse<AssetMap>>> GetTable(ITableStateRequest request, CancellationToken cancellationToken)
	{
		var query = GetQuery();

		return await GetTable(query, request, cancellationToken);
	}

	/// <inheritdoc />
	public async Task<Result<ITableStateResponse<AssetMap>>> GetTable(IQueryable<AssetMap> query, ITableStateRequest request, CancellationToken cancellationToken)
	{
		var count = await query.CountAsync(cancellationToken);

		if (request.Skip.HasValue)
		{
			query = query.Skip(request.Skip.Value);
		}

		if (request.Take.HasValue)
		{
			query = query.Take(request.Take.Value);
		}

		var items = await query.ToListAsync(cancellationToken);

		return new TableStateResponse<AssetMap>(items, count);
	}

	/// <inheritdoc />
	public async Task<Result> UpdateAsync(Guid id, Guid? assetId, CancellationToken cancellationToken)
	{
		try
		{
			var assetMap = await dataContext.Maps
				.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
			if (assetMap is null)
			{
				Log.Information(NotFoundMessage);
				return Result.Fail(NotFoundMessage);
			}

			var sameAssetId = await dataContext.Maps
				.FirstOrDefaultAsync(x => x.Id != id && x.AssetId == assetId, cancellationToken);
			if (sameAssetId is not null)
			{
				Log.Information(AlreadyExistsMessage);
				return Result.Fail(AlreadyExistsMessage);
			}

			assetMap.AssetId = assetId;
			_ = await dataContext.SaveChangesAsync(cancellationToken);
			return Result.Success();
		}
		catch (Exception ex)
		{
			return Result.Fail(ex.Message);
		}
	}

	/// <inheritdoc />
	public async Task<Result> UpdateIsOnlineStatusAsync(
		Guid id,
		bool isOnline,
		CancellationToken cancellationToken)
	{
		try
		{
			var assetMap = await dataContext.Maps
				.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
			if (assetMap is null)
			{
				Log.Information(NotFoundMessage);
				return Result.Fail(NotFoundMessage);
			}

			assetMap.IsOnline = isOnline;

			_ = await dataContext.SaveChangesAsync(cancellationToken);

			return Result.Success();
		}
		catch (Exception ex)
		{
			return Result.Fail(ex.Message);
		}
	}
}