using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Conversations.Application.Abstractions;
using Knot.Contracts.Conversations.Domain;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Modules.TelegramImport.Application.Abstractions;
using Knot.Modules.TelegramImport.Application.TelegramImport;
using Knot.Modules.TelegramImport.Application.TelegramImport.DTOs;
using Knot.Modules.TelegramImport.Infrastructure.Background;
using Knot.Shared.Kernel.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Knot.Modules.TelegramImport.Infrastructure.Background;

public class TelegramImportWorker : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<TelegramImportWorker> _logger;
    private readonly IImportJobStore _jobStore;

    public TelegramImportWorker(
        IServiceProvider serviceProvider, 
        ILogger<TelegramImportWorker> logger, 
        IImportJobStore jobStore)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
        _jobStore = jobStore;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Telegram Import Worker started.");
        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.Delay(5000, stoppingToken);
        }
    }

    public async Task ProcessImportAsync(ExecuteImportCommand request, Guid jobId, Guid chatId, string zipPath, CancellationToken ct)
    {
        using var scope = _serviceProvider.CreateScope();
        var parser = scope.ServiceProvider.GetRequiredService<ITelegramHtmlParser>();
        var msgRepo = scope.ServiceProvider.GetRequiredService<IMessageRepository>();
        var reactionRepo = scope.ServiceProvider.GetRequiredService<IMessageReactionRepository>();
        var chatRepo = scope.ServiceProvider.GetRequiredService<IChatRepository>();
        var uow = scope.ServiceProvider.GetRequiredService<IChatsUnitOfWork>();
        var fileStorage = scope.ServiceProvider.GetRequiredService<IFileStorageService>();

        var jobInfo = new ImportJobInfo 
        { 
            JobId = jobId, 
            Status = ImportJobStatus.Processing,
            TotalMessages = request.TotalMessages,
            ProcessedMessages = 0
        };
        _jobStore.AddOrUpdate(jobInfo);

        try
        {
            var chat = await chatRepo.GetByIdAsync(chatId, ct);
            if (chat == null) throw new Exception("Chat not found");

            _logger.LogInformation("Processing messages for ChatId: {ChatId}, JobId: {JobId}", chatId, jobId);

            // TRY TO FIND THE BEST ENCODING (UTF8 or CP866)
            ZipArchive archive;
            try 
            {
                // Try UTF8 first
                archive = ZipFile.OpenRead(zipPath);
                var messagesHtml = archive.Entries.FirstOrDefault(e => e.Name.Equals("messages.html", StringComparison.OrdinalIgnoreCase));
                if (messagesHtml == null)
                {
                    // If not found in root, maybe it's CP866
                    archive.Dispose();
                    archive = ZipFile.Open(zipPath, ZipArchiveMode.Read, Encoding.GetEncoding(866));
                }
            }
            catch 
            {
                archive = ZipFile.OpenRead(zipPath);
            }

            using (archive)
            {
                var entries = archive.Entries
                    .Where(e => e.FullName.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && 
                               (e.Name.StartsWith("messages", StringComparison.OrdinalIgnoreCase) || 
                                e.Name.Equals("export_results.html", StringComparison.OrdinalIgnoreCase)))
                    .OrderBy(e => e.FullName.Length) 
                    .ThenBy(e => e.FullName)
                    .ToList();

                var allMessages = new List<Knot.Modules.TelegramImport.Application.TelegramImport.DTOs.TelegramMessage>();

                foreach (var entry in entries)
                {
                    var entryPath = entry.FullName.Replace("\\", "/");
                    var lastSlash = entryPath.LastIndexOf('/');
                    var baseDir = lastSlash >= 0 ? entryPath.Substring(0, lastSlash + 1) : "";

                    using var stream = entry.Open();
                    var messages = await parser.ParseMessagesAsync(stream, baseDir, ct);
                    allMessages.AddRange(messages);
                }

                var orderedMessages = allMessages
                    .OrderBy(m => m.CreatedAt)
                    .ThenBy(m => m.OrderIndex)
                    .ToList();

                _logger.LogInformation("Total messages parsed: {Count}", orderedMessages.Count);

                var telegramToKnotIdMap = new Dictionary<string, Guid>();
                var processedCount = 0;

                foreach (var m in orderedMessages)
                {
                    Guid senderId = request.Mapping.TryGetValue(m.SenderName ?? "", out var sid) ? sid : request.CurrentUserId;
                    Guid? replyToId = m.ReplyToId != null && telegramToKnotIdMap.TryGetValue(m.ReplyToId, out var rid) ? rid : null;
                    
                    Guid knotMsgId = Guid.NewGuid();
                    Message? knotMsg = null;

                    if (m.Media != null && m.Media.Any())
                    {
                        var mediaType = m.Media.Any(mi => mi.MimeType.StartsWith("image")) ? MediaType.Image : 
                                        m.Media.Any(mi => mi.MimeType.StartsWith("video")) ? MediaType.Video :
                                        m.Media.Any(mi => mi.MimeType.Contains("voice")) ? MediaType.Voice : MediaType.File;

                        var mediaMsg = new MediaMessage(knotMsgId, chat.Id, senderId, mediaType, m.Content, replyToId, null, m.CreatedAt, true);

                        foreach (var mediaItem in m.Media)
                        {
                            var targetPath = mediaItem.FilePath.Replace("\\", "/").TrimStart('/');
                            var unescapedPath = Uri.UnescapeDataString(targetPath);
                            
                            var zipEntry = archive.Entries.FirstOrDefault(e => {
                                var entryPath = e.FullName.Replace("\\", "/").TrimStart('/');
                                return entryPath.Equals(targetPath, StringComparison.OrdinalIgnoreCase) || 
                                       entryPath.Equals(unescapedPath, StringComparison.OrdinalIgnoreCase);
                            });
                            
                            // IF NOT FOUND, try a desperate match by stripping path and comparing filenames
                            if (zipEntry == null)
                            {
                                var fileName = Path.GetFileName(targetPath);
                                zipEntry = archive.Entries.FirstOrDefault(e => e.Name.Equals(fileName, StringComparison.OrdinalIgnoreCase));
                            }

                            if (zipEntry != null)
                            {
                                using var mediaStream = zipEntry.Open();
                                string itemType = "file";
                                string mimeToUpload = mediaItem.MimeType;
                                
                                if (mediaItem.MimeType.Equals("image/gif", StringComparison.OrdinalIgnoreCase)) 
                                {
                                    itemType = "gif";
                                    if (mediaItem.FileName.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase)) mimeToUpload = "video/mp4";
                                }
                                else if (mediaItem.MimeType.StartsWith("image")) itemType = "image";
                                else if (mediaItem.MimeType.StartsWith("video")) itemType = "video";
                                else if (mediaItem.MimeType.Contains("voice") || mediaItem.MimeType.Contains("audio")) itemType = "audio";

                                var fileId = await fileStorage.UploadFileAsync(mediaStream, mediaItem.FileName, mimeToUpload);
                                mediaMsg.AddMedia(itemType, $"/api/files/{fileId}", mediaItem.FileName, zipEntry.Length, mediaItem.Duration);
                            }
                            else 
                            {
                                _logger.LogWarning("[Import] Media entry NOT FOUND in ZIP: {Path}", targetPath);
                            }
                        }

                        if (mediaMsg.Media.Any()) knotMsg = mediaMsg;
                    }

                    if (knotMsg == null)
                    {
                        knotMsg = new TextMessage(knotMsgId, chat.Id, senderId, m.Content, replyToId, null, null, m.CreatedAt, true);
                    }

                    chat.IncrementSequenceId();
                    knotMsg.SetSequenceId(chat.LastMessageSequenceId);
                    msgRepo.Add(knotMsg);

                    if (m.Reactions != null)
                    {
                        foreach (var r in m.Reactions)
                        {
                            foreach (var name in r.UserNames)
                            {
                                var userId = request.Mapping.TryGetValue(name, out var mappedId) && mappedId != Guid.Empty ? mappedId : request.CurrentUserId;
                                var knotReaction = new MessageReaction(knotMsgId, userId, r.Emoji);
                                await reactionRepo.AddAsync(knotReaction, ct);
                            }
                        }
                    }

                    telegramToKnotIdMap[m.Id] = knotMsgId;
                    processedCount++;

                    if (processedCount % 50 == 0)
                    {
                        await uow.SaveChangesAsync(ct);
                        jobInfo.ProcessedMessages = processedCount;
                        _jobStore.AddOrUpdate(jobInfo);
                    }
                }

                chat.CompleteImport();
                await uow.SaveChangesAsync(ct);

                _logger.LogInformation("Import Completed. Messages: {Count}", processedCount);
                jobInfo.ProcessedMessages = processedCount;
                jobInfo.Status = ImportJobStatus.Completed;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during Telegram import process.");
            jobInfo.Status = ImportJobStatus.Failed;
            jobInfo.ErrorMessage = ex.Message;
        }
        finally
        {
            _jobStore.AddOrUpdate(jobInfo);
            try { if (File.Exists(zipPath)) File.Delete(zipPath); } catch { }
            TelegramImportState.TempZips.TryRemove(request.Token, out _);
        }
    }
}
