using AutoMapper;
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Dto.Activity;
using salesbook.Shared.Core.Dto.Contact;
using salesbook.Shared.Core.Dto.PageState;
using salesbook.Shared.Core.Entity;
using salesbook.Shared.Core.Helpers;
using salesbook.Shared.Core.Helpers.Enum;
using salesbook.Shared.Core.Interface;
using salesbook.Shared.Core.Interface.IntegryApi;
using salesbook.Shared.Core.Interface.System.Network;
using System.Linq.Expressions;
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account;

namespace salesbook.Maui.Core.Services;

public class ManageDataService(
    LocalDbService localDb,
    IMapper mapper,
    UserListState userListState,
    IIntegryApiService integryApiService,
    INetworkService networkService,
    IUserSession userSession
) : IManageDataService
{
    public Task<List<T>> GetTable<T>(Expression<Func<T, bool>>? whereCond = null) where T : new() =>
        localDb.Get(whereCond);

    public async Task<List<AnagClie>> GetClienti(WhereCondContact? whereCond)
    {
        List<AnagClie> clienti = [];
        whereCond ??= new WhereCondContact();
        whereCond.OnlyContact = true;

        if (networkService.ConnectionAvailable)
        {
            var response = await integryApiService.RetrieveAnagClie(
                new CRMAnagRequestDTO
                {
                    CodAnag = whereCond.CodAnag,
                    FlagStato = whereCond.FlagStato,
                    PartIva = whereCond.PartIva,
                    ReturnPersRif = !whereCond.OnlyContact
                }
            );

            clienti = response.AnagClie ?? [];
        }
        else
        {
            clienti = await localDb.Get<AnagClie>(x =>
                (whereCond.FlagStato != null && x.FlagStato.Equals(whereCond.FlagStato)) ||
                (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) ||
                (whereCond.PartIva == null && whereCond.FlagStato == null)
            );
        }

        return clienti;
    }

    public async Task<List<PtbPros>> GetProspect(WhereCondContact? whereCond)
    {
        List<PtbPros> prospect = [];
        whereCond ??= new WhereCondContact();
        whereCond.OnlyContact = true;

        if (networkService.ConnectionAvailable)
        {
            var response = await integryApiService.RetrieveProspect(
                new CRMProspectRequestDTO
                {
                    CodPpro = whereCond.CodAnag,
                    PartIva = whereCond.PartIva,
                    ReturnPersRif = !whereCond.OnlyContact
                }
            );

            prospect = response.PtbPros ?? [];
        }
        else
        {
            prospect = await localDb.Get<PtbPros>(x =>
                (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) ||
                (whereCond.PartIva == null)
            );
        }

        return prospect;
    }

    public async Task<List<ContactDTO>> GetContact(WhereCondContact? whereCond, DateTime? lastSync)
    {
        whereCond ??= new WhereCondContact();

        // Ottengo liste locali
        var contactList = await localDb.Get<AnagClie>(x =>
            (whereCond.FlagStato != null && x.FlagStato == whereCond.FlagStato) ||
            (whereCond.PartIva != null && x.PartIva == whereCond.PartIva) ||
            (whereCond.PartIva == null && whereCond.FlagStato == null)
        );

        var prospectList = await localDb.Get<PtbPros>(x =>
            (whereCond.PartIva != null && x.PartIva == whereCond.PartIva) ||
            (whereCond.PartIva == null)
        );

        if (networkService.ConnectionAvailable)
        {
            var clienti = await integryApiService.RetrieveAnagClie(
                new CRMAnagRequestDTO
                {
                    CodAnag = whereCond.CodAnag,
                    FlagStato = whereCond.FlagStato,
                    PartIva = whereCond.PartIva,
                    ReturnPersRif = !whereCond.OnlyContact,
                    FilterDate = lastSync
                }
            );

            var prospect = await integryApiService.RetrieveProspect(
                new CRMProspectRequestDTO
                {
                    CodPpro = whereCond.CodAnag,
                    PartIva = whereCond.PartIva,
                    ReturnPersRif = !whereCond.OnlyContact,
                    FilterDate = lastSync
                }
            );
            
            _ = UpdateDbUsers(new UsersSyncResponseDTO
            {
                AnagClie = clienti.AnagClie,
                VtbDest = clienti.VtbDest,
                VtbCliePersRif = clienti.VtbCliePersRif,
                PtbPros = prospect.PtbPros,
                PtbProsRif = prospect.PtbProsRif
            });

            if (lastSync != null)
            {
                contactList = MergeLists(
                    contactList,
                    clienti.AnagClie,
                    x => x.CodAnag
                );

                prospectList = MergeLists(
                    prospectList,
                    prospect.PtbPros,
                    x => x.CodPpro
                );
            }
            else
            {
                contactList = clienti.AnagClie;
                prospectList = prospect.PtbPros;
            }
        }

        // Mappa i contatti
        var contactMapper = mapper.Map<List<ContactDTO>>(contactList);

        // Mappa i prospect
        var prospectMapper = mapper.Map<List<ContactDTO>>(prospectList);

        contactMapper.AddRange(prospectMapper);

        return contactMapper;
    }
    
    private static List<T>? MergeLists<T, TKey>(List<T>? localList, List<T>? apiList, Func<T, TKey> keySelector)
    {
        if (apiList == null && localList != null) return localList;
        if (apiList != null && localList == null) return apiList;
        if (apiList == null && localList == null) return null;

        var dictionary = localList!.ToDictionary(keySelector);

        foreach (var apiItem in apiList)
        {
            var key = keySelector(apiItem);
            dictionary[key] = apiItem;
        }

        return dictionary.Values.ToList();
    }

    public async Task<ContactDTO?> GetSpecificContact(string codAnag, bool isContact)
    {
        if (isContact)
        {
            var contact = (await localDb.Get<AnagClie>(x => x.CodAnag != null && x.CodAnag.Equals(codAnag)))
                .LastOrDefault();

            return contact == null ? null : mapper.Map<ContactDTO>(contact);
        }
        else
        {
            var contact = (await localDb.Get<PtbPros>(x => x.CodPpro != null && x.CodPpro.Equals(codAnag)))
                .LastOrDefault();

            return contact == null ? null : mapper.Map<ContactDTO>(contact);
        }
    }

    public async Task<List<ActivityDTO>> GetActivityTryLocalDb(WhereCondActivity whereCond)
    {
        var activities = await localDb.Get<StbActivity>(x =>
            (whereCond.ActivityId != null && x.ActivityId != null && whereCond.ActivityId.Equals(x.ActivityId)) ||
            (whereCond.Start != null && whereCond.End != null && x.EffectiveTime == null &&
             x.EstimatedTime >= whereCond.Start && x.EstimatedTime <= whereCond.End) ||
            (x.EffectiveTime >= whereCond.Start && x.EffectiveTime <= whereCond.End) ||
            (whereCond.ActivityId == null && (whereCond.Start == null || whereCond.End == null))
        );

        if (activities.IsNullOrEmpty() && networkService.ConnectionAvailable)
        {
            activities = await integryApiService.RetrieveActivity(
                new CRMRetrieveActivityRequestDTO
                {
                    StarDate = whereCond.Start,
                    EndDate = whereCond.End,
                    ActivityId = whereCond.ActivityId
                }
            );

            _ = UpdateDb(activities);
        }
        else return [];

        return await MapActivity(activities);
    }

    public async Task<List<ActivityDTO>> GetActivity(WhereCondActivity whereCond, bool useLocalDb)
    {
        List<StbActivity>? activities;

        if (networkService.ConnectionAvailable && !useLocalDb)
        {
            activities = await integryApiService.RetrieveActivity(
                new CRMRetrieveActivityRequestDTO
                {
                    StarDate = whereCond.Start,
                    EndDate = whereCond.End,
                    ActivityId = whereCond.ActivityId
                }
            );

            _ = UpdateDb(activities);
        }
        else return await GetActivityTryLocalDb(whereCond);

        return await MapActivity(activities);
    }

    public async Task<List<ActivityDTO>> MapActivity(List<StbActivity>? activities)
    {
        if (activities == null) return [];

        var codJcomList = activities
            .Select(x => x.CodJcom)
            .Where(x => !string.IsNullOrEmpty(x))
            .Distinct().ToList();

        var jtbComtList = await localDb.Get<JtbComt>(x => codJcomList.Contains(x.CodJcom));

        var codAnagList = activities
            .Select(x => x.CodAnag)
            .Where(x => !string.IsNullOrEmpty(x))
            .Distinct().ToList();

        IDictionary<string, string?>? distinctUser = null;

        if (userListState.AllUsers != null)
        {
            distinctUser = userListState.AllUsers
                .Where(x => codAnagList.Contains(x.CodContact))
                .ToDictionary(x => x.CodContact, x => x.RagSoc);
        }

        var returnDto = activities
            .Select(activity =>
            {
                if (activity.CodJcom is "0000" && userSession.ProfileDb != null &&
                    userSession.ProfileDb.Equals("smetar", StringComparison.OrdinalIgnoreCase))
                {
                    activity.CodJcom = null;
                }

                var dto = mapper.Map<ActivityDTO>(activity);

                if (activity is { AlarmTime: not null, EstimatedTime: not null })
                {
                    var minuteBefore = activity.EstimatedTime.Value - activity.AlarmTime.Value;
                    dto.MinuteBefore = (int)Math.Abs(minuteBefore.TotalMinutes);

                    dto.NotificationDate = dto.MinuteBefore == 0 ? activity.EstimatedTime : activity.AlarmTime;
                }

                if (activity.CodJcom != null)
                {
                    dto.Category = ActivityCategoryEnum.Commessa;
                }
                else
                {
                    dto.Category = activity.CodAnag != null ? ActivityCategoryEnum.Interna : ActivityCategoryEnum.Memo;
                }

                if (dto.Category != ActivityCategoryEnum.Memo && activity.CodAnag != null)
                {
                    string? ragSoc;

                    if (distinctUser != null && (distinctUser.TryGetValue(activity.CodAnag, out ragSoc) ||
                                                 distinctUser.TryGetValue(activity.CodAnag, out ragSoc)))
                    {
                        dto.Cliente = ragSoc;
                    }
                }

                dto.Commessa = jtbComtList.Find(x => x.CodJcom.Equals(dto.CodJcom));
                return dto;
            })
            .ToList();

        return returnDto;
    }

    private Task UpdateDbUsers(UsersSyncResponseDTO response)
    {
        return Task.Run(async () =>
        {
            try
            {
                if (response.AnagClie != null)
                {
                    await localDb.InsertOrUpdate(response.AnagClie);

                    if (response.VtbDest != null) await localDb.InsertOrUpdate(response.VtbDest);
                    if (response.VtbCliePersRif != null) await localDb.InsertOrUpdate(response.VtbCliePersRif);
                }

                if (response.PtbPros != null)
                {
                    await localDb.InsertOrUpdate(response.PtbPros);

                    if (response.PtbProsRif != null) await localDb.InsertOrUpdate(response.PtbProsRif);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                SentrySdk.CaptureException(e);
                throw;
            }
        });
    }

    private Task UpdateDb<T>(List<T>? entityList)
    {
        return Task.Run(() =>
        {
            if (entityList == null) return;
            _ = localDb.InsertOrUpdate(entityList);
        });
    }

    public Task InsertOrUpdate<T>(List<T> listToSave) =>
        localDb.InsertOrUpdate(listToSave);

    public Task InsertOrUpdate<T>(T objectToSave) =>
        localDb.InsertOrUpdate<T>([objectToSave]);

    public async Task DeleteProspect(string codPpro)
    {
        var persRifList = await GetTable<PtbProsRif>(x => x.CodPpro!.Equals(codPpro));

        if (!persRifList.IsNullOrEmpty())
        {
            foreach (var persRif in persRifList)
            {
                await localDb.Delete(persRif);
            }
        }

        var ptbPros = (await GetTable<PtbPros>(x => x.CodPpro!.Equals(codPpro))).FirstOrDefault();

        if (ptbPros != null)
        {
            await localDb.Delete(ptbPros);
        }
    }

    public Task Delete<T>(T objectToDelete) =>
        localDb.Delete(objectToDelete);

    public async Task DeleteActivity(ActivityDTO activity)
    {
        await localDb.Delete(
            (await GetTable<StbActivity>(x => x.ActivityId.Equals(activity.ActivityId))).Last()
        );
    }

    public Task ClearDb() =>
        localDb.ResetDb();
}