namespace MyOffice.Services.Dashboard;

using Core.Extensions;
using Data.Repositories.Account;
using Data.Repositories.Currency;
using MyOffice.Data.Models.Accounts;
using MyOffice.Services.Dashboard.Domain;

public class DashboardService
{
	private readonly IAccountRepository _accountRepository;
	private readonly ICurrencyRateRepository _currencyRateRepository;

	public DashboardService(
		IAccountRepository accountRepository,
		ICurrencyRateRepository currencyRateRepository
	)
	{
		_accountRepository = accountRepository;
		_currencyRateRepository = currencyRateRepository;
	}

	public async Task<DashboardData> GetDashboardRestDataAsync(
		Guid userId,
		CancellationToken cancellationToken = default
	)
	{
		var rests = await _accountRepository.GetRestAtDateAsync(userId, DateTime.UtcNow, cancellationToken);

		return new DashboardData
		{
			Balance = rests
				.Where(x => (x.Type == AccountAccessTypeEnum.balance || x.Type == AccountAccessTypeEnum.credit) && x.CurrencyRate.HasValue)
				.Sum(x => x.Balance * (x.CurrencyRate * x.CurrencyQuantity)),

			BalanceDebit = rests
				.Where(x => x.Type == AccountAccessTypeEnum.balance && x.CurrencyRate.HasValue)
				.Sum(x => x.Balance * (x.CurrencyRate * x.CurrencyQuantity)),

			BalanceCredit = rests
				.Where(x => x.Type == AccountAccessTypeEnum.credit && x.CurrencyRate.HasValue)
				.Sum(x => x.Balance * (x.CurrencyRate * x.CurrencyQuantity)),

			BalanceRests = rests
				.Where(x => x.Type == AccountAccessTypeEnum.balance)
				.Where(x => x.CurrencyRate.HasValue && x.Balance.HasValue && x.Balance != 0)
				.Select(x => new DashboardRestData
				{
					Id = x.Id,
					Name = x.Name,
					Balance = x.Balance,
					CurrencyName = x.CurrencyName!,
					CurrencyShortName = x.CurrencyShortName!,
					CurrencyRate = x.CurrencyRate,
					CurrencyQuantity = x.CurrencyQuantity,
				})
				.OrderByDescending(x => x.BalanceAtRate)
				.ToList(),
		};
	}

	public async Task<DashboardIncomeData> GetDashboardIncomeDataAsync(
		Guid userId,
		DateTime from,
		DateTime to,
		Guid? category,
		CancellationToken cancellationToken = default
	)
	{
		var data = category.HasValue
			? await _accountRepository.GetIncomeByCategoryAsync(userId, category.Value, from.ToUtc(), to.ToUtc(), cancellationToken)
			: await _accountRepository.GetIncomeByCategoriesAsync(userId, from.ToUtc(), to.ToUtc(), cancellationToken);

		data = data
			.Where(x => x.Amount != 0)
			.ToList();

		var currencies = data
			.Select(x => x.CurrencyId)
			.Distinct()
			.ToList();

		var rates = (await _currencyRateRepository.GetLastRatesAsync(userId, currencies, to.ToUtc(), cancellationToken))
			.ToDictionary(x => x.Key, x => x.Value.Rate * x.Value.Quantity);

		return BuildIncomeData(data, rates);
	}

	public async Task<DashboardIncomeData> GetDashboardOutcomeDataAsync(
		Guid userId,
		DateTime from,
		DateTime to,
		Guid? category,
		CancellationToken cancellationToken = default
	)
	{
		var data = category.HasValue
			? await _accountRepository.GetOutcomeByCategoryAsync(userId, category.Value, from.ToUtc(), to.ToUtc(), cancellationToken)
			: await _accountRepository.GetOutcomeByCategoriesAsync(userId, from.ToUtc(), to.ToUtc(), cancellationToken);

		data = data
			.Where(x => x.Amount != 0)
			.ToList();

		var currencies = data
			.Select(x => x.CurrencyId)
			.Distinct()
			.ToList();

		var rates = (await _currencyRateRepository.GetLastRatesAsync(userId, currencies, to.ToUtc(), cancellationToken))
			.ToDictionary(x => x.Key, x => x.Value.Rate * x.Value.Quantity);

		return BuildIncomeData(data, rates);
	}

	private static DashboardIncomeData BuildIncomeData(
		List<MotionTotalSimple> data,
		Dictionary<string, decimal> rates
	)
	{
		return new DashboardIncomeData
		{
			Data = data.Select(x => new
				{
					Id = x.Id.ToShort(),
					Name = x.Name,
					ValueRaw = x.Amount,
					Value = x.Amount * rates.FirstOrDefault(r => r.Key == x.CurrencyId).Value,
				})
				.GroupBy(x => new { x.Id, x.Name })
				.Select(x => new DashboardIncomeDataItem
				{
					Id = x.Key.Id,
					Name = x.Key.Name,
					Value = x.Sum(s => s.Value),
					ValueRaw = x.Sum(s => s.ValueRaw),
				})
				.OrderBy(x => x.Name)
				.ToList(),

			Details = data.Select(x => new
				{
					Id = x.CurrencyId,
					Name = x.CurrencyName,
					ValueRaw = x.Amount,
					Value = x.Amount * rates.FirstOrDefault(r => r.Key == x.CurrencyId).Value,
				})
				.GroupBy(x => new { x.Id, x.Name })
				.Select(x => new DashboardIncomeDataItem
				{
					Id = x.Key.Id,
					Name = x.Key.Name,
					Value = x.Sum(s => s.Value),
					ValueRaw = x.Sum(s => s.ValueRaw),
				})
				.OrderBy(x => x.Name)
				.ToList(),
		};
	}
}
