using AutoMapper;
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage;
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Dto.Activity;
using salesbook.Shared.Core.Dto.Contact;
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 salesbook.Shared.Core.Dto.PageState;

namespace salesbook.Maui.Core.Services;

public class ManageDataService(
    LocalDbService localDb,
    IMapper mapper,
    UserListState userListState,
    IIntegryApiService integryApiService,
    INetworkService networkService
) : 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)
    {
        List<AnagClie>? contactList;
        List<PtbPros>? prospectList;
        whereCond ??= new WhereCondContact();

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

            response.AnagClie = clienti.AnagClie;
            response.VtbDest = clienti.VtbDest;
            response.VtbCliePersRif = clienti.VtbCliePersRif;

            var prospect = await integryApiService.RetrieveProspect(
                new CRMProspectRequestDTO
                {
                    CodPpro = whereCond.CodAnag,
                    PartIva = whereCond.PartIva,
                    ReturnPersRif = !whereCond.OnlyContact,
                    FilterDate = lastSync
                }
            );

            response.PtbPros = prospect.PtbPros;
            response.PtbProsRif = prospect.PtbProsRif;

            _ = UpdateDbUsers(response);

            contactList = clienti.AnagClie;
            prospectList = prospect.PtbPros;
        }
        else
        {
            contactList = 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)
            );
            prospectList = await localDb.Get<PtbPros>(x =>
                (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) ||
                (whereCond.PartIva == null)
            );
        }

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

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

        contactMapper.AddRange(prospectMapper);

        return contactMapper;
    }

    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)
    {
        List<StbActivity>? activities;

        activities = await localDb.Get<StbActivity>(x =>
            (whereCond.ActivityId != null && x.ActivityId != null && whereCond.ActivityId.Equals(x.ActivityId)) ||
            (whereCond.Start != null && whereCond.End != null && x.EffectiveDate == null &&
             x.EstimatedDate >= whereCond.Start && x.EstimatedDate <= whereCond.End) ||
            (x.EffectiveDate >= whereCond.Start && x.EffectiveDate <= 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);
        }

        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
        {
            activities = await localDb.Get<StbActivity>(x =>
                (whereCond.ActivityId != null && x.ActivityId != null && whereCond.ActivityId.Equals(x.ActivityId)) ||
                (whereCond.Start != null && whereCond.End != null && x.EffectiveDate == null &&
                 x.EstimatedDate >= whereCond.Start && x.EstimatedDate <= whereCond.End) ||
                (x.EffectiveDate >= whereCond.Start && x.EffectiveDate <= whereCond.End) ||
                (whereCond.ActivityId == null && (whereCond.Start == null || whereCond.End == null))
            );
        }

        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 =>
            {
                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 () =>
        {
            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);
            }
        });
    }

    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();
}