using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using Tiku.Application.Learning;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Learning;

namespace Tiku.Infrastructure.Learning;

internal sealed class PracticeReportService(LearningServiceDependencies dependencies)
    : LearningActivityServiceBase(dependencies), IPracticeReportService
{
    public async Task<PracticeSessionReportItem> GetPracticeSessionReportAsync(
        LearningActor actor,
        PracticeSessionFilter filter,
        CancellationToken cancellationToken = default)
    {
        if (!filter.PracticeSessionId.HasValue)
            throw new LearningValidationException("practice_session_id_required", "Practice session id is required.");

        var report = await learningPersistence.PracticeSessionReports
            .AsNoTracking()
            .SingleOrDefaultAsync(
                item =>
                    item.TenantId == actor.TenantId &&
                    item.UserId == actor.UserId &&
                    item.PracticeSessionId == filter.PracticeSessionId.Value,
                cancellationToken);

        if (report is null)
            throw new LearningResourceNotFoundException("practice_report_not_found",
                "Practice session report was not found.");

        return ToItem(report);
    }

    public async Task<LearningList<PracticeSessionReportItem>> GetPracticeReportsAsync(
        LearningActor actor,
        PracticeSessionFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = learningPersistence.PracticeSessionReports
            .AsNoTracking()
            .Where(report =>
                report.TenantId == actor.TenantId &&
                report.UserId == actor.UserId);

        if (filter.BlueprintId.HasValue) query = query.Where(report => report.BlueprintId == filter.BlueprintId.Value);

        if (!string.IsNullOrWhiteSpace(filter.Mode)) query = query.Where(report => report.Mode == filter.Mode.Trim());

        var items = await query
            .OrderByDescending(report => report.SubmittedAt)
            .Take(ResolveLimit(filter.Limit))
            .ToArrayAsync(cancellationToken);

        return new LearningList<PracticeSessionReportItem>(items.Select(ToItem).ToArray());
    }

    public async Task<LearningList<PracticeHistoryItem>> GetPracticeHistoryAsync(
        LearningActor actor,
        PracticeSessionFilter filter,
        CancellationToken cancellationToken = default)
    {
        var query = learningPersistence.PracticeSessions
            .AsNoTracking()
            .Where(session =>
                session.TenantId == actor.TenantId &&
                session.UserId == actor.UserId);

        if (!string.IsNullOrWhiteSpace(filter.Mode)) query = query.Where(session => session.Mode == filter.Mode.Trim());

        var rows = await query
            .GroupJoin(
                learningPersistence.PracticeSessionReports.AsNoTracking(),
                session => new { session.TenantId, PracticeSessionId = session.Id },
                report => new { report.TenantId, report.PracticeSessionId },
                (session, reports) => new { session, report = reports.FirstOrDefault() })
            .OrderByDescending(row => row.session.FinishedAt ?? row.session.StartedAt)
            .Take(ResolveLimit(filter.Limit))
            .ToArrayAsync(cancellationToken);
        var now = DateTimeOffset.UtcNow;
        var items = rows
            .Select(row => new PracticeHistoryItem(
                row.session.Id,
                row.session.Mode,
                row.session.TargetType,
                row.session.TargetId,
                row.session.BlueprintId,
                row.session.CollectionId,
                row.session.EntryId,
                row.session.ContentNodeId,
                row.session.QuestionCount,
                row.report?.AnsweredCount ?? 0,
                row.report?.CorrectCount ?? 0,
                row.report?.WrongCount ?? 0,
                row.session.StartedAt,
                row.session.FinishedAt,
                row.session.ExpiresAt,
                ResolvePracticeSessionHistoryStatus(row.session, now),
                row.report?.Id,
                row.report?.Score,
                row.report?.TotalScore,
                row.report?.Accuracy))
            .Where(item => string.IsNullOrWhiteSpace(filter.Status) || item.Status == filter.Status.Trim())
            .ToArray();

        return new LearningList<PracticeHistoryItem>(items);
    }
}
