namespace MyOffice.Services.Currency;

using AutoMapper;

using Core;
using Data.Models.Currencies;
using Data.Repositories.Currency;
using Domain;

public class CurrencyService
{
	private readonly ICurrencyGlobalRepository _currencyGlobalRepository;
	private readonly ICurrencyRepository _currencyRepository;
	private readonly ICurrencyRateRepository _currencyRateRepository;
	private readonly IMapper _mapper;

	public CurrencyService(
		ICurrencyGlobalRepository currencyGlobalRepository,
		ICurrencyRepository currencyRepository,
		ICurrencyRateRepository currencyRateRepository,
		IMapper mapper
	)
	{
		_currencyGlobalRepository = currencyGlobalRepository;
		_currencyRepository = currencyRepository;
		_currencyRateRepository = currencyRateRepository;
		_mapper = mapper;
	}

	public async Task<List<CurrencyGlobalDto>> GetGlobalAllAsync(CancellationToken cancellationToken = default)
	{
		return _mapper.Map<List<CurrencyGlobalDto>>(await _currencyGlobalRepository.GetAllAsync(cancellationToken));
	}

	public async Task<List<CurrencyDto>> GetAllAsync(Guid userId, CancellationToken cancellationToken = default)
	{
		return _mapper.Map<List<CurrencyDto>>(await _currencyRepository.GetAllAsync(userId, cancellationToken));
	}

	public async Task<List<CurrencyWithRateDto>> GetAllWithRatesAsync(Guid userId, CancellationToken cancellationToken = default)
	{
		var list = await _currencyRepository.GetAllAsync(userId, cancellationToken);

		var result = new List<CurrencyWithRateDto>(list.Count);
		foreach (var currency in list)
		{
			var rates = await _currencyRateRepository.GetLastRatesAsync(currency.Id, cancellationToken: cancellationToken);
			result.Add(new CurrencyWithRateDto
			{
				Currency = _mapper.Map<CurrencyDto>(currency),
				Rate = _mapper.Map<CurrencyRateDto>(rates.FirstOrDefault()),
			});
		}

		return result;
	}

	public async Task<Exec<CurrencyDto, CurrencyAddStatus>> CurrencyAddAsync(
		Guid userId,
		CurrencyDto input,
		CancellationToken cancellationToken = default)
	{
		if (input == null)
			throw new ArgumentNullException(nameof(input));

		var result = new Exec<CurrencyDto, CurrencyAddStatus>(CurrencyAddStatus.success);

		var exists = await _currencyRepository.GetByGlobalCurrencyAsync(userId, input.CurrencyGlobalId, cancellationToken);
		if (exists != null)
		{
			return result.Set(_mapper.Map<CurrencyDto>(exists), CurrencyAddStatus.exists);
		}

		var currency = new Currency
		{
			Id = Guid.NewGuid(),
			UserId = userId,
			CurrencyGlobalId = input.CurrencyGlobalId,
			Name = input.Name,
			ShortName = input.ShortName,
		};

		if (!await _currencyRepository.AddAsync(currency, cancellationToken))
		{
			return result.Set(CurrencyAddStatus.failed);
		}

		return result.Set(_mapper.Map<CurrencyDto>(currency));
	}

	public async Task<Exec<CurrencyDto, CurrencyEditStatus>> CurrencyUpdateAsync(
		Guid userId,
		Guid currencyId,
		CurrencyEdit currency,
		CancellationToken cancellationToken = default)
	{
		if (currency == null)
			throw new ArgumentNullException(nameof(currency));

		var result = new Exec<CurrencyDto, CurrencyEditStatus>(CurrencyEditStatus.success);

		var exists = await _currencyRepository.GetAsync(userId, currencyId, cancellationToken);
		if (exists == null)
		{
			return result.Set(CurrencyEditStatus.not_found);
		}

		exists.Name = currency.Name;
		exists.ShortName = currency.ShortName;
		exists.IsPrimary = currency.IsPrimary;

		if (!await _currencyRepository.UpdateAsync(exists, cancellationToken))
		{
			return result.Set(CurrencyEditStatus.failed);
		}

		if (exists.IsPrimary)
		{
			var primaries = await _currencyRepository.GetPrimariesAsync(userId, cancellationToken);
			foreach (var primary in primaries)
			{
				if (primary.Id != exists.Id)
				{
					primary.IsPrimary = false;
					await _currencyRepository.UpdateAsync(primary, cancellationToken);
				}
			}
		}

		return result.Set(_mapper.Map<CurrencyDto>(exists));
	}

	public async Task<Exec<CurrencyRateDto, CurrencyAddRateStatus>> CurrencyRateAddAsync(
		Guid userId,
		Guid currencyId,
		CurrencyRateDto input,
		CancellationToken cancellationToken = default)
	{
		if (input == null)
			throw new ArgumentNullException(nameof(input));

		var result = new Exec<CurrencyRateDto, CurrencyAddRateStatus>(CurrencyAddRateStatus.success);

		var currency = await _currencyRepository.GetAsync(userId, currencyId, cancellationToken);
		if (currency == null)
		{
			return result.Set(CurrencyAddRateStatus.not_found);
		}

		var currencyRate = new CurrencyRate
		{
			CurrencyId = currency.Id,
			DateTime = input.DateTime.Date,
			Rate = input.Rate,
			Quantity = input.Quantity,
		};

		var rates = await _currencyRateRepository.GetAtDateAsync(currencyRate.CurrencyId, currencyRate.DateTime, cancellationToken);
		var rate = rates.Find(x => x.Rate == currencyRate.Rate);

		if (rate == null)
		{
			if (!await _currencyRateRepository.AddRateAsync(currencyRate, cancellationToken))
			{
				return result.Set(CurrencyAddRateStatus.failed);
			}

			// TODO: more logic to fix current rate
			if (currencyRate.DateTime.Date == DateTime.UtcNow.Date || !currency.CurrentRateId.HasValue)
			{
				currency.CurrencyGlobal = null;
				currency.CurrentRateId = currencyRate.Id;
				await _currencyRepository.UpdateAsync(currency, cancellationToken);
			}

			rate = currencyRate;
		}

		return result.Set(_mapper.Map<CurrencyRateDto>(rate));
	}

	public async Task<Exec<CurrencyDto, GeneralExecStatus>> RemoveAsync(
		Guid userId,
		Guid id,
		CancellationToken cancellationToken = default)
	{
		var result = new Exec<CurrencyDto, GeneralExecStatus>(GeneralExecStatus.success);

		var currency = await _currencyRepository.GetAsync(userId, id, cancellationToken);
		if (currency == null)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		if (!await _currencyRepository.RemoveAsync(currency, cancellationToken))
		{
			return result.Set(GeneralExecStatus.failure);
		}

		return result.Set(_mapper.Map<CurrencyDto>(currency));
	}
}
