using GeoRitmMover.GeoRitm.Data;
using GeoRitmMover.Mover.Data;

using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain;
using Monitoring.Core.Domain.Primitives;

namespace GeoRitmMover.Services.Implementations.TableMovers;
public class AreaTableMover(
	ILogger<AreaTableMover> logger,
	IContexts contexts
) : ITableMover
{
	public async Task Move(CancellationToken cancellationToken)
	{
		logger.LogInformation("Moving areas");

		var count = await contexts.Source.Areas.CountAsync(cancellationToken);

		var maps = await contexts.Middleware.Areas
			.Select(x => x.To)
			.ToListAsync(cancellationToken);

		for (var i = 0; i < count; i += Constants.BatchSize)
		{
			await PassBatch(i, Constants.BatchSize, cancellationToken);
		}

		logger.LogInformation("Done moving areas");
	}

	private async Task PassBatch(int startIndex, int count,
		CancellationToken cancellationToken)
	{
		logger.LogInformation("Start area batch with index: {startIndex}", startIndex);

		var sourceAreaList = await contexts.Source.Areas
			.AsNoTracking()
			.OrderBy(x => x.Id)
			.Skip(startIndex)
			.Take(count)
			.ToListAsync(cancellationToken);
		var sourceAreasDictionary = sourceAreaList.ToDictionary(x => x.Id);

		var areaMapList = sourceAreaList
			.Select(x => AreaMap.Create(Guid.NewGuid(), x.Id, new(Guid.NewGuid())))
			.ToList();

		var sourceObjectIdList = sourceAreaList.Select(x => x.ObjectId).Distinct().ToList();
		var assetGroupMaps = await contexts.Middleware.Assets
			.AsNoTracking()
			.Where(x => sourceObjectIdList.Contains(x.From))
			.ToListAsync(cancellationToken);
		var assetMapDictionary = assetGroupMaps
			.ToDictionary(x => x.From, x => x.To);

		var areaList = areaMapList
			.Select(map =>
			{
				var sourceArea = sourceAreasDictionary[map.From];

				var id = map.To;
				var index = new AreaIndex(sourceArea.Num);
				var name = GetName(sourceArea, "Area: " + sourceArea.Num);
				var description = GetDescription(sourceArea, "Area: " + sourceArea.Num);
				var assetId = assetMapDictionary[sourceArea.ObjectId];

				return Area.Create(id, index, name, description, assetId);
			})
			.ToList();

		using var middlewareTransaction = await contexts.Middleware.Database
			.BeginTransactionAsync(cancellationToken);

		try
		{
			logger.LogInformation("Try save area maps");

			await contexts.Middleware.Areas.AddRangeAsync(areaMapList, cancellationToken);
			_ = await contexts.Middleware.SaveChangesAsync(cancellationToken);

			logger.LogInformation("Area maps is saved");

			using var transaction = await contexts.Destination.Database
				.BeginTransactionAsync(cancellationToken);

			try
			{
				logger.LogInformation("Try save areas");

				await contexts.Destination.Areas.AddRangeAsync(areaList, cancellationToken);
				_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

				logger.LogInformation("Areas is saved");

				await transaction.CommitAsync(cancellationToken);
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Error while saving areas");
				await transaction.RollbackAsync(cancellationToken);

				throw;
			}

			await middlewareTransaction.CommitAsync(cancellationToken);
		}
		catch (Exception ex)
		{
			logger.LogError(ex, "Error while saving area maps");
			await middlewareTransaction.RollbackAsync(cancellationToken);
		}

		logger.LogInformation("Done moving area batch: start = {start}, count = {count}", startIndex, Constants.BatchSize);
	}

	private static AreaDescription GetDescription(SourceArea sourceArea, string defaultName)
	{
		if (string.IsNullOrEmpty(sourceArea.Comment)
			|| sourceArea.Comment.Length < AreaDescription.MinLength
			|| sourceArea.Comment.Length > AreaDescription.MaxLength)
		{
			return AreaDescription.Create(defaultName);
		}

		return AreaDescription.Create(sourceArea.Comment);
	}

	private static AreaName GetName(SourceArea sourceArea, string defaultName)
	{
		if (string.IsNullOrEmpty(sourceArea.Name)
			|| sourceArea.Name.Length < AreaName.MinLength
			|| sourceArea.Name.Length > AreaName.MaxLength)
		{
			return AreaName.Create(defaultName);
		}

		return AreaName.Create(sourceArea.Name);
	}
}
