using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Application.TenantAdmin;
using Tiku.Domain.Learning;
using Tiku.Domain.Tenancy;
using PointActivityClaimStatus = Tiku.Domain.Commerce.PointActivityClaimStatus;
using PointExchangeOrderStatus = Tiku.Domain.Commerce.PointExchangeOrderStatus;

namespace Tiku.Infrastructure.TenantAdmin;

public sealed partial class TenantAdminDirectService
{
    public async Task<CatalogList<TenantSupervisionRuleItem>> GetSupervisionRulesAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await RequireDataScopeAsync(actor, cancellationToken);
        return new CatalogList<TenantSupervisionRuleItem>(
            await GetSupervisionRulesCoreAsync(actor.TenantId, cancellationToken));
    }

    public async Task<ContentManagementResult<TenantSupervisionRuleItem>> UpsertSupervisionRuleAsync(
        TenantAdminActor actor,
        UpsertTenantSupervisionRuleCommand command,
        CancellationToken cancellationToken = default)
    {
        await RequireDataScopeAsync(actor, cancellationToken);
        var code = NormalizeCode(command.Code);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.Title);
        var rules = (await GetSupervisionRulesCoreAsync(actor.TenantId, cancellationToken)).ToList();
        var item = new TenantSupervisionRuleItem(
            code,
            command.Title.Trim(),
            command.Enabled,
            command.DaysWithoutCheckIn,
            command.MaxQuestionsAnsweredToday,
            Normalize(command.FollowupType) ?? StudentFollowupType.Risk.ToString(),
            Normalize(command.Priority) ?? StudentFollowupPriority.High.ToString(),
            JsonObjectOrDefault(command.Metadata));
        rules.RemoveAll(rule => string.Equals(rule.Code, code, StringComparison.Ordinal));
        rules.Add(item);
        await SaveSupervisionRulesCoreAsync(actor.TenantId, rules.OrderBy(rule => rule.Code).ToArray(),
            cancellationToken);
        await AddAuditAsync(actor, "tenant.supervision_rule.upserted", "tenant_settings", actor.TenantId,
            cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantSupervisionRuleItem>(item);
    }

    public async Task<TenantSupervisionPreview> PreviewSupervisionAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        return new TenantSupervisionPreview(await BuildSupervisionRiskStudentsAsync(actor, scope, cancellationToken));
    }

    public async Task<TenantSupervisionGenerateResult> GenerateSupervisionFollowupsAsync(
        TenantAdminActor actor,
        TenantSupervisionGenerateCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        await AssertTenantMemberAsync(actor.TenantId, command.AssignedToUserId, cancellationToken);
        var riskStudents = await BuildSupervisionRiskStudentsAsync(actor, scope, cancellationToken);
        if (command.UserIds is { Count: > 0 })
        {
            var selected = command.UserIds.Where(id => id != Guid.Empty).Distinct().ToHashSet();
            riskStudents = riskStudents.Where(item => selected.Contains(item.UserId)).ToArray();
        }

        var created = 0;
        foreach (var student in riskStudents)
        {
            if (await dbContext.TenantStudentFollowups.AnyAsync(item =>
                        item.TenantId == actor.TenantId &&
                        item.StudentUserId == student.UserId &&
                        item.Status != StudentFollowupStatus.Done &&
                        item.FollowupType == StudentFollowupType.Risk,
                    cancellationToken))
                continue;

            dbContext.TenantStudentFollowups.Add(new TenantStudentFollowup
            {
                TenantId = actor.TenantId,
                StudentUserId = student.UserId,
                AssignedToUserId = command.AssignedToUserId,
                Title = "学习风险督导",
                Description = string.Join("；", student.Reasons),
                FollowupType = StudentFollowupType.Risk,
                Priority = StudentFollowupPriority.High,
                Status = StudentFollowupStatus.Open,
                DueAt = command.DueAt,
                CreatedBy = actor.UserId,
                UpdatedBy = actor.UserId,
                Metadata = JsonSerializer.SerializeToElement(new { ruleCodes = student.RuleCodes })
            });
            created++;
        }

        await AddAuditAsync(actor, "tenant.supervision_followups.generated", "tenant_student_followups", actor.TenantId,
            cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new TenantSupervisionGenerateResult(created);
    }

    public async Task<TenantFollowupReport> GetFollowupReportAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await RequireDataScopeAsync(actor, cancellationToken);
        var now = DateTimeOffset.UtcNow;
        return new TenantFollowupReport(
            await dbContext.TenantStudentFollowups.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == StudentFollowupStatus.Open,
                cancellationToken),
            await dbContext.TenantStudentFollowups.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == StudentFollowupStatus.InProgress,
                cancellationToken),
            await dbContext.TenantStudentFollowups.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == StudentFollowupStatus.Done,
                cancellationToken),
            await dbContext.TenantStudentFollowups.CountAsync(item =>
                    item.TenantId == actor.TenantId &&
                    item.DueAt.HasValue &&
                    item.DueAt < now &&
                    item.Status != StudentFollowupStatus.Done &&
                    item.Status != StudentFollowupStatus.Cancelled,
                cancellationToken));
    }

    public async Task<TenantFeedbackReport> GetFeedbackReportAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await RequireDataScopeAsync(actor, cancellationToken);
        return new TenantFeedbackReport(
            await dbContext.Reports.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == ReportStatus.Pending, cancellationToken),
            await dbContext.Reports.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == ReportStatus.Accepted, cancellationToken),
            await dbContext.Reports.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == ReportStatus.Rejected, cancellationToken),
            await dbContext.Reports.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == ReportStatus.Resolved, cancellationToken),
            await dbContext.Reports.CountAsync(
                item => item.TenantId == actor.TenantId && item.Status == ReportStatus.Closed, cancellationToken));
    }

    public async Task<TenantPointRiskReport> GetPointRiskReportAsync(
        TenantAdminActor actor,
        CancellationToken cancellationToken = default)
    {
        await RequireDataScopeAsync(actor, cancellationToken);
        var negativeScoreUsers = await dbContext.Users
            .Where(user => user.Score < 0 &&
                           dbContext.TenantMemberships.Any(member =>
                               member.TenantId == actor.TenantId &&
                               member.UserId == user.Id &&
                               member.Status == MembershipStatus.Active))
            .CountAsync(cancellationToken);
        var since = DateTimeOffset.UtcNow.AddDays(-7);
        var highClaimUsers = await dbContext.PointActivityClaims
            .Where(item => item.TenantId == actor.TenantId && item.Status == PointActivityClaimStatus.Claimed &&
                           item.ClaimedAt >= since)
            .GroupBy(item => item.UserId)
            .Where(group => group.Sum(item => item.Points) >= 1000)
            .CountAsync(cancellationToken);
        var cancelledExchangeOrders = await dbContext.PointExchangeOrders.CountAsync(
            item => item.TenantId == actor.TenantId && item.Status == PointExchangeOrderStatus.Cancelled,
            cancellationToken);
        return new TenantPointRiskReport(negativeScoreUsers, highClaimUsers, cancelledExchangeOrders);
    }
}