using JSMR.Application.Scanning.Contracts;
using JSMR.Application.Scanning.Ports;
using JSMR.Domain.Entities;
using JSMR.Domain.Enums;
using JSMR.Infrastructure.Common.Languages;
using JSMR.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace JSMR.Infrastructure.Ingestion;

public class EnglishVoiceWorkUpdater(AppDbContext dbContext, ILanguageIdentifier languageIdentifier) : IVoiceWorkUpdater
{
    public async Task<VoiceWorkUpsertResult[]> UpsertAsync(IReadOnlyCollection<VoiceWorkIngest> ingests, CancellationToken cancellationToken)
    {
        EnglishVoiceWorkUpsertContext upsertContext = await CreateUpsertContextAsync(ingests, cancellationToken);

        foreach (VoiceWorkIngest ingest in ingests)
        {
            Validate(ingest, upsertContext);

            VoiceWorkUpsertResult result = upsertContext.Results[ingest.ProductId];

            if (upsertContext.VoiceWorks.TryGetValue(ingest.ProductId, out VoiceWork? voiceWork))
            {
                result.VoiceWorkId = upsertContext.VoiceWorks[ingest.ProductId].VoiceWorkId;
            }

            if (result.Issues.Count > 0)
            {
                result.Status = VoiceWorkUpsertStatus.Skipped;
                continue;
            }
            
            result.Status = UpsertEnglishVoiceWork(ingest, upsertContext);
        }

        await dbContext.SaveChangesAsync(cancellationToken);

        return [.. upsertContext.Results.Select(x => x.Value)];
    }

    private async Task<EnglishVoiceWorkUpsertContext> CreateUpsertContextAsync(IReadOnlyCollection<VoiceWorkIngest> ingests, CancellationToken cancellationToken)
    {
        string[] makerIds = [.. ingests.Select(i => i.MakerId).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct()];
        string[] productIds = [.. ingests.Select(i => i.ProductId).Distinct()];

        EnglishVoiceWorkUpsertContext upsertContext = new(
            Circles: await dbContext.Circles
                .Where(c => makerIds.Contains(c.MakerId))
                .ToDictionaryAsync(c => c.MakerId, cancellationToken),
            VoiceWorks: await dbContext.VoiceWorks
                .Where(v => productIds.Contains(v.ProductId))
                .Include(v => v.EnglishVoiceWorks)
                .Include(v => v.Localizations)
                .ToDictionaryAsync(v => v.ProductId, cancellationToken),
            Results: productIds.ToDictionary(
                productId => productId,
                productId => new VoiceWorkUpsertResult()
                {
                    ProductId = productId,
                    Title  = ingests.First(x => x.ProductId == productId).Title
                }
            )
        );

        return upsertContext;
    }

    private void Validate(VoiceWorkIngest ingest, EnglishVoiceWorkUpsertContext upsertContext)
    {
        VoiceWorkUpsertResult result = upsertContext.Results[ingest.ProductId];

        bool isTitleEnglish = languageIdentifier.GetLanguage(ingest.Title) == Language.English;
        bool isDescriptionEnglish = !string.IsNullOrWhiteSpace(ingest.Description) && languageIdentifier.GetLanguage(ingest.Description) == Language.English;

        if (!isTitleEnglish && !isDescriptionEnglish)
        {
            string message = $"Product title and/or description is not in English";
            result.Issues.Add(new(message, VoiceWorkUpsertIssueSeverity.Information));
            return;
        }

        if (upsertContext.Circles.ContainsKey(ingest.MakerId) == false)
        {
            string message = $"Unable to find circle for maker id: {ingest.MakerId}";
            result.Issues.Add(new(message, VoiceWorkUpsertIssueSeverity.Error));
            return;
        }

        if (upsertContext.VoiceWorks.ContainsKey(ingest.ProductId) == false)
        {
            string message = $"Unable to find voice work for product id: {ingest.ProductId}";
            result.Issues.Add(new(message, VoiceWorkUpsertIssueSeverity.Error));
        }
    }

    private VoiceWorkUpsertStatus UpsertEnglishVoiceWork(VoiceWorkIngest ingest, EnglishVoiceWorkUpsertContext upsertContext)
    {
        EnglishVoiceWork englishVoiceWork = GetOrAddEnglishVoiceWork(ingest, upsertContext);
        englishVoiceWork.ProductName = ingest.Title;
        englishVoiceWork.Description = ingest.Description;
        englishVoiceWork.IsValid = true;

        return dbContext.Entry(englishVoiceWork).State switch
        {
            EntityState.Added => VoiceWorkUpsertStatus.Inserted,
            EntityState.Modified => VoiceWorkUpsertStatus.Updated,
            _ => VoiceWorkUpsertStatus.Unchanged,
        };
    }

    private EnglishVoiceWork GetOrAddEnglishVoiceWork(VoiceWorkIngest ingest, EnglishVoiceWorkUpsertContext upsertContext)
    {
        VoiceWork voiceWork = upsertContext.VoiceWorks[ingest.ProductId];
        EnglishVoiceWork? englishVoiceWork = voiceWork.EnglishVoiceWorks.FirstOrDefault();

        if (englishVoiceWork is null)
        {
            englishVoiceWork = new EnglishVoiceWork
            {
                VoiceWork = voiceWork,
                ProductName = string.Empty,
                Description = string.Empty
            };

            dbContext.EnglishVoiceWorks.Add(englishVoiceWork);
            //upsertContext.VoiceWorks[ingest.ProductId] = voiceWork;
        }

        return englishVoiceWork;
    }
}