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 ZoneTableMover(
	ILogger<ZoneTableMover> logger,
	IContexts contexts
) : ITableMover
{
	public async Task Move(CancellationToken cancellationToken)
	{
		logger.LogInformation("Moving zones");

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

		var maps = await contexts.Middleware.Zones
			.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 zones");
	}

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

		var sourceZoneList = await contexts.Source.Zones
			.AsNoTracking()
			.OrderBy(x => x.Id)
			.Skip(startIndex)
			.Take(count)
			.ToListAsync(cancellationToken);
		var sourceZoneDictionary = sourceZoneList.ToDictionary(x => x.Id);

		var zoneMapList = sourceZoneList
			.Select(x => ZoneMap.Create(Guid.NewGuid(), x.Id, new(Guid.NewGuid())))
			.ToList();

		var sourceAreaIdList = sourceZoneList.Select(x => x.AreaId).Distinct().ToList();
		var areaGroupMaps = await contexts.Middleware.Areas
			.AsNoTracking()
			.Where(x => sourceAreaIdList.Contains(x.From))
			.ToListAsync(cancellationToken);
		var areaMapDictionary = areaGroupMaps
			.ToDictionary(x => x.From, x => x.To);

		var sourceAreaList = await contexts.Source.Areas
			.AsNoTracking()
			.Where(x => sourceAreaIdList.Contains(x.Id))
			.ToListAsync(cancellationToken);
		var sourceAreasDictionary = sourceAreaList
			.ToDictionary(x => x.Id);

		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 zoneList = zoneMapList
			.Select(map =>
			{
				var sourceZone = sourceZoneDictionary[map.From];
				var sourceArea = sourceAreasDictionary[sourceZone.AreaId];

				var index = new ZoneIndex(sourceZone.Num);
				var name = GetName(sourceZone, $"Zone {index.Value}");
				var description = GetDescription(sourceZone, $"Zone {index.Value}");

				return Zone.Create(
					map.To,
					index,
					name,
					description,
					areaMapDictionary[sourceArea.Id]);
			})
			.ToList();

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

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

			await contexts.Middleware.Zones.AddRangeAsync(zoneMapList, cancellationToken);
			_ = await contexts.Middleware.SaveChangesAsync(cancellationToken);

			logger.LogInformation("Zone maps are saved");

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

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

				await contexts.Destination.Zones.AddRangeAsync(zoneList, cancellationToken);
				_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

				logger.LogInformation("Zones are saved");

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

				throw;
			}

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

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

	private static ZoneDescription GetDescription(SourceZone sourceZone, string defaultName)
	{
		if (string.IsNullOrEmpty(sourceZone.Comment)
			|| sourceZone.Comment.Length < ZoneDescription.MinLength
			|| sourceZone.Comment.Length > ZoneDescription.MaxLength)
		{
			return ZoneDescription.Create(defaultName);
		}

		return ZoneDescription.Create(sourceZone.Comment);
	}

	private static ZoneName GetName(SourceZone sourceZone, string defaultName)
	{
		if (string.IsNullOrEmpty(sourceZone.Name)
			|| sourceZone.Name.Length < ZoneName.MinLength
			|| sourceZone.Name.Length > ZoneName.MaxLength)
		{
			return ZoneName.Create(defaultName);
		}

		return ZoneName.Create(sourceZone.Name);
	}
}
