using System.Text.Json;

using GeoRitmMover.Mover.Data;

using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain;
using Monitoring.Core.Domain.AssetGroupManagement;
using Monitoring.Core.Domain.AssetManagement;
using Monitoring.Core.Domain.OrganizationManagement;

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

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

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

		var sourceObjectSettings = await contexts.Source.Settings
			.ToListAsync(cancellationToken);
		var objectSettingsDictionary = sourceObjectSettings
			.ToDictionary(x => new Tuple<int, string>(x.ObjectId, x.SettingsKey), x => x.Value);

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

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

	private async Task PassBatch(
		int startIndex,
		int count,
		Dictionary<Tuple<int, string>, string?> objectSettingsDictionary,
		CancellationToken cancellationToken)
	{
		logger.LogInformation("Start index: {startIndex}", startIndex);

		var sourceObjectList = await contexts.Source.Objects
			.AsNoTracking()
			.OrderBy(x => x.Id)
			.Skip(startIndex)
			.Take(count)
			.ToListAsync(cancellationToken);
		var sourceAssetsDictionary = sourceObjectList.ToDictionary(x => x.Id);

		var assetMapList = sourceObjectList
			.Select(x => AssetMap.Create(Guid.NewGuid(), x.Id, new(Guid.NewGuid())))
			.ToList();

		var sourceObjectIdList = sourceObjectList.Select(x => x.Id).ToList();
		var sourceObjectGroupMemberList = await contexts.Source.ObjectGroupMembers
			.AsNoTracking()
			.Where(x => sourceObjectIdList.Contains(x.ObjectId))
			.ToListAsync(cancellationToken);
		var sourceObjectGroupMembersDictionary = sourceObjectGroupMemberList
			.GroupBy(x => x.ObjectId)
			.ToDictionary(x => x.Key, x => x.Select(x => x.GroupId).ToList());

		var assetGroupMaps = await contexts.Middleware.Groups
			.AsNoTracking()
			.ToListAsync(cancellationToken);
		var groupMapDictionary = assetGroupMaps.ToDictionary(x => x.From, x => x.To);

		var destinationGroups = await contexts.Destination.AssetGroups
			.ToListAsync(cancellationToken);

		var assetList = assetMapList
			.Select(map =>
			{
				var sourceObject = sourceAssetsDictionary[map.From];

				GetGeo(objectSettingsDictionary, map, out var latitude, out var longitude);

				GetResponsible(objectSettingsDictionary, map,
					out var responsiblePersons,
					out var duressPassword,
					out var identificationPassword);

				var groupIds = GetGroupIdList(map, sourceObjectGroupMembersDictionary,
					groupMapDictionary);

				var sourceName = sourceObject.Name ?? string.Empty;
				if (sourceName.Length > 100)
				{
					sourceName = sourceName[..100];
				}
				var name = AssetName.Create(sourceName);
				var description = AssetDescription.Create(objectSettingsDictionary.GetValueOrDefault(new(map.From, "DESCRIPTION")) ?? "no description");
				var organizationId = new OrganizationId(Constants.OrganizationId);

				var asset = Asset.Create(
					map.To,
					name,
					description,
					organizationId,
					new Address(
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "Country")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "Region")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "City")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "Street")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "House")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "Building")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "AppNumber")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "GateCode")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "IntercomCode")) ?? string.Empty
					),
					new Owner(
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "VRC_OWNER")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "OwnerPassword")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "PHONE1")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "PHONE2")) ?? string.Empty
					),
					new Passwords(
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "IDENTIFICATION_PASSWORD")) ?? string.Empty,
						objectSettingsDictionary.GetValueOrDefault(new(map.From, "DURESS_PASSWORD")) ?? string.Empty
					),
					new Coordinates(longitude, latitude),
					null,
					Imei.Empty);

				if (groupIds.Count != 0)
				{
					var groups = destinationGroups.Where(x => groupIds.Contains(x.Id)).ToList();
					asset.Groups.AddRange(groups);
				}

				if (responsiblePersons is not null)
				{
					asset.ResponsiblePersons.AddRange(responsiblePersons);
				}

				return asset;
			})
			.ToList();

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

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

			await contexts.Middleware.Assets.AddRangeAsync(assetMapList, cancellationToken);
			_ = await contexts.Middleware.SaveChangesAsync(cancellationToken);

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

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

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

				await contexts.Destination.Assets.AddRangeAsync(assetList, cancellationToken);
				_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

				logger.LogInformation("Assets is saved");

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

				throw;
			}

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

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

	private static List<AssetGroupId> GetGroupIdList(
		AssetMap map,
		Dictionary<int, List<int>> sourceObjectGroupMembersDictionary,
		Dictionary<int, AssetGroupId> groupMapDictionary)
	{
		if (sourceObjectGroupMembersDictionary.TryGetValue(map.From, out var groupIdList))
		{
			return groupIdList
				.Select(x => groupMapDictionary[x])
				.ToList();
		}

		return [];
	}

	private static void GetResponsible(Dictionary<Tuple<int, string>, string?> objectSettingsDictionary, AssetMap map, out List<ResponsiblePerson> responsiblePersons, out string duressPassword, out string identificationPassword)
	{
		var sourceResponsivePersons = objectSettingsDictionary.GetValueOrDefault(new(map.From, "RESPONSIVE_PERSONS")) ?? string.Empty;
		responsiblePersons = [];
		duressPassword = string.Empty;
		identificationPassword = string.Empty;
		if (!string.IsNullOrEmpty(sourceResponsivePersons))
		{
			var responsive =
				JsonSerializer.Deserialize<SourceResponsivePersons>(sourceResponsivePersons);
			if (responsive is not null)
			{
				identificationPassword = responsive.Pass1;
				duressPassword = responsive.Pass2;
				responsiblePersons = responsive.Persons.Select(x => new ResponsiblePerson
				{
					Id = Guid.NewGuid(),
					Name = x.Name,
					AssetId = map.To,
					Key = x.Key,
					Note = x.Comment,
					Phone1 = x.Phone1,
					Phone2 = x.Phone2,
				}).ToList();
			}
		}
	}

	private static void GetGeo(Dictionary<Tuple<int, string>, string?> objectSettingsDictionary,
		AssetMap map, out float? latitude, out float? longitude)
	{
		latitude = null;
		longitude = null;
		if (float.TryParse(objectSettingsDictionary.GetValueOrDefault(new(map.From, "Lat")), out var lat))
		{
			latitude = lat;
		}

		if (float.TryParse(objectSettingsDictionary.GetValueOrDefault(new(map.From, "Lon")), out var lon))
		{
			longitude = lon;
		}
	}
}
