using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kavita.Database;
using Kavita.Models.Entities;
using Kavita.Models.Entities.Enums;
using Kavita.Models.Entities.Progress;
using Kavita.Services.Reading;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Kavita.Server.ManualMigrations.v0._8._9;


/// <summary>
/// v0.8.9 - Convert past progress into Reading Sessions
/// </summary>
public class MigrateProgressToReadingSessions : ManualMigration
{
    private const int BatchSize = 1000;
    public const string Name = nameof(MigrateProgressToReadingSessions);

    private static AppUserReadingSessionActivityData CreateSessionActivityDataFromProgress(AppUserProgress progress, Chapter chapter, MangaFormat format)
    {

        var sessionDate = progress.LastModified.Date;
        var sessionDateUtc = progress.LastModifiedUtc.Date;

        var totalWordsRead = 0;
        var isEpub = format == MangaFormat.Epub;

        if (isEpub && chapter.WordCount > 0 && chapter.Pages > 0)
        {
            totalWordsRead = (int)Math.Round(chapter.WordCount * (progress.PagesRead / (1.0f * chapter.Pages)));
        }

        // NOTE: I'm seeing a lot of off by 1 pages read issues here

        var estimatedTime = ReaderService.GetTimeEstimate(totalWordsRead, progress.PagesRead, isEpub);

        var endDate = new DateTime(
            Math.Min(
                sessionDate.AddHours(estimatedTime.AvgHours).Ticks,
                sessionDate.Date.AddDays(1).AddTicks(-1).Ticks
            ), DateTimeKind.Local
        );
        var endDateUtc = new DateTime(
            Math.Min(
                sessionDateUtc.AddHours(estimatedTime.AvgHours).Ticks,
                sessionDateUtc.Date.AddDays(1).AddTicks(-1).Ticks
            ), DateTimeKind.Utc
        );


        return new AppUserReadingSessionActivityData
        {
            ChapterId = progress.ChapterId,
            VolumeId = progress.VolumeId,
            SeriesId = progress.SeriesId,
            LibraryId = progress.LibraryId,
            StartPage = 0,
            EndPage = progress.PagesRead,
            StartBookScrollId = null,
            EndBookScrollId = progress.BookScrollId,
            StartTime = sessionDate,
            StartTimeUtc = sessionDateUtc,
            EndTime = endDate,
            EndTimeUtc = endDateUtc,
            PagesRead = progress.PagesRead,
            WordsRead = totalWordsRead,
            TotalPages = chapter.Pages,
            TotalWords = chapter.WordCount,
            ClientInfo = null,
            DeviceIds = [],
            Format = format
        };
    }

    protected override string MigrationName => nameof(MigrateProgressToReadingSessions);
    protected override async Task ExecuteAsync(DataContext context, ILogger<Program> logger)
    {
        try
        {
            var totalProgressRecords = await context.AppUserProgresses.CountAsync();
            if (totalProgressRecords > 0)
            {
                logger.LogInformation("[{Scope}] Found {Count} progress records to migrate", MigrationName, totalProgressRecords);

                var totalBatches = (int)Math.Ceiling(totalProgressRecords / (double)BatchSize);
                var migratedCount = 0;

                for (var batchNumber = 0; batchNumber < totalBatches; batchNumber++)
                {
                    // Join with Chapter to get TotalPages and WordCount
                    var progressBatch = await context.AppUserProgresses
                        .AsNoTracking()
                        .Where(p => p.PagesRead > 0)
                        .OrderBy(p => p.Id)
                        .Skip(batchNumber * BatchSize)
                        .Take(BatchSize)
                        .Join(context.Chapter,
                            p => p.ChapterId,
                            c => c.Id,
                            (progress, chapter) => new { Progress = progress, Chapter = chapter })
                        .Join(context.Series,
                            p => p.Progress.SeriesId,
                            s => s.Id,
                            (combo, series) => new {combo.Progress, combo.Chapter, series.Format })
                        .ToListAsync();

                    var sessions = new List<AppUserReadingSession>();

                    var groupedProgress = progressBatch
                        .Where(item => item.Progress.PagesRead > 0)
                        .GroupBy(item => new
                        {
                            item.Progress.AppUserId,
                            item.Progress.LastModified.Date,
                        });

                    foreach (var group in groupedProgress)
                    {
                        var firstItem = group.FirstOrDefault();
                        if (firstItem == null) continue;

                        var sessionDate = firstItem.Progress.LastModified.Date;
                        var sessionDateUtc = sessionDate.ToUniversalTime();

                        var activityData = group.Select(item =>
                            CreateSessionActivityDataFromProgress(item.Progress, item.Chapter, item.Format))
                            .ToList();

                        var session = new AppUserReadingSession
                        {
                            AppUserId = group.Key.AppUserId,
                            StartTime = sessionDate,
                            StartTimeUtc = sessionDateUtc,
                            EndTime = activityData.Max(a => a.EndTime),
                            EndTimeUtc = activityData.Max(a => a.EndTimeUtc),
                            IsActive = false,
                            ActivityData = activityData,
                            Created = sessionDate,
                            CreatedUtc = sessionDateUtc,
                            LastModified = sessionDate,
                            LastModifiedUtc = sessionDateUtc
                        };

                        sessions.Add(session);
                    }

                    if (sessions.Count > 0)
                    {
                        await context.AppUserReadingSession.AddRangeAsync(sessions);
                        await context.SaveChangesAsync();
                        migratedCount += sessions.Count;
                    }

                    logger.LogInformation("[{Scope}] Migrated batch {Current}/{Total} ({Count} sessions)",
                        MigrationName, batchNumber + 1, totalBatches, migratedCount);
                }

                logger.LogInformation("[{Scope}] Migration complete: {Count} sessions created from {Total} progress records",
                    MigrationName, migratedCount, totalProgressRecords);
            }
            else
            {
                logger.LogInformation("[{Scope}] No progress records found to migrate", MigrationName);
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "[{Scope}] Error during MigrateProgressToReadingSessions migration", MigrationName);
            throw;
        }
    }
}
