using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Application.TenantAdmin;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Security;

namespace Tiku.Infrastructure.TenantAdmin;

public sealed partial class TenantAdminDirectService
{
    public async Task<CatalogList<TenantAdminStudentNoteItem>> GetStudentNotesAsync(
        TenantAdminActor actor,
        TenantAdminStudentActivityFilter filter,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        var regionIds = scope.RegionIds.ToArray();
        var classIds = scope.ClassIds.ToArray();
        var query = dbContext.TenantStudentNotes.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId)
            .ApplyDataScope(
                scope,
                item => item.StudentUserId == actor.UserId || item.CreatedBy == actor.UserId,
                item => dbContext.StudentProfiles.Any(profile =>
                            profile.TenantId == actor.TenantId &&
                            profile.UserId == item.StudentUserId &&
                            profile.RegionId.HasValue &&
                            regionIds.Contains(profile.RegionId.Value)) ||
                        dbContext.TenantClassMembers.Any(member =>
                            member.TenantId == actor.TenantId &&
                            member.UserId == item.StudentUserId &&
                            member.Status == TenantClassMemberStatus.Active &&
                            classIds.Contains(member.ClassId)));
        if (filter.StudentUserId.HasValue)
            query = query.Where(item => item.StudentUserId == filter.StudentUserId.Value);

        var items = await query
            .OrderByDescending(item => item.IsPinned)
            .ThenByDescending(item => item.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(item => ToNoteItem(item))
            .ToArrayAsync(cancellationToken);
        return new CatalogList<TenantAdminStudentNoteItem>(items);
    }

    public async Task<ContentManagementResult<TenantAdminStudentNoteItem>> UpsertStudentNoteAsync(
        TenantAdminActor actor,
        UpsertTenantAdminStudentNoteCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.Content);
        await AssertStudentAsync(actor, scope, command.StudentUserId, cancellationToken);
        TenantStudentNote? item = null;
        if (command.Id.HasValue)
        {
            var regionIds = scope.RegionIds.ToArray();
            var classIds = scope.ClassIds.ToArray();
            item = await dbContext.TenantStudentNotes
                .Where(note => note.TenantId == actor.TenantId && note.Id == command.Id.Value)
                .ApplyDataScope(
                    scope,
                    note => note.StudentUserId == actor.UserId || note.CreatedBy == actor.UserId,
                    note => dbContext.StudentProfiles.Any(profile =>
                                profile.TenantId == actor.TenantId &&
                                profile.UserId == note.StudentUserId &&
                                profile.RegionId.HasValue &&
                                regionIds.Contains(profile.RegionId.Value)) ||
                            dbContext.TenantClassMembers.Any(member =>
                                member.TenantId == actor.TenantId &&
                                member.UserId == note.StudentUserId &&
                                member.Status == TenantClassMemberStatus.Active &&
                                classIds.Contains(member.ClassId)))
                .FirstOrDefaultAsync(cancellationToken);
            if (item is null)
                throw new TenantAdminDirectException("Student note was not found.", "student_note_not_found");
        }

        var isNew = item is null;
        item ??= new TenantStudentNote
        {
            Id = command.Id ?? Guid.NewGuid(), TenantId = actor.TenantId, StudentUserId = command.StudentUserId,
            CreatedBy = actor.UserId
        };
        item.NoteType = ParseEnum(command.NoteType, StudentNoteType.General, "invalid_student_note_type");
        item.Content = command.Content.Trim();
        item.Visibility = ParseEnum(command.Visibility, StudentNoteVisibility.TenantStaff,
            "invalid_student_note_visibility");
        item.IsPinned = command.IsPinned ?? item.IsPinned;
        item.Metadata = JsonObjectOrDefault(command.Metadata);
        item.UpdatedBy = actor.UserId;
        if (isNew) dbContext.TenantStudentNotes.Add(item);

        await AddAuditAsync(actor, "tenant.student_note.upserted", "tenant_student_notes", item.Id, cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantAdminStudentNoteItem>(ToNoteItem(item));
    }

    public async Task<CatalogList<TenantAdminStudentFollowupItem>> GetStudentFollowupsAsync(
        TenantAdminActor actor,
        TenantAdminStudentActivityFilter filter,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        var regionIds = scope.RegionIds.ToArray();
        var classIds = scope.ClassIds.ToArray();
        var query = dbContext.TenantStudentFollowups.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId)
            .ApplyDataScope(
                scope,
                item => item.StudentUserId == actor.UserId || item.AssignedToUserId == actor.UserId ||
                        item.CreatedBy == actor.UserId,
                item => (item.ClassId.HasValue && classIds.Contains(item.ClassId.Value)) ||
                        dbContext.StudentProfiles.Any(profile =>
                            profile.TenantId == actor.TenantId &&
                            profile.UserId == item.StudentUserId &&
                            profile.RegionId.HasValue &&
                            regionIds.Contains(profile.RegionId.Value)) ||
                        dbContext.TenantClassMembers.Any(member =>
                            member.TenantId == actor.TenantId &&
                            member.UserId == item.StudentUserId &&
                            member.Status == TenantClassMemberStatus.Active &&
                            classIds.Contains(member.ClassId)));
        if (filter.StudentUserId.HasValue)
            query = query.Where(item => item.StudentUserId == filter.StudentUserId.Value);

        if (!string.IsNullOrWhiteSpace(filter.Status))
            query = query.Where(item =>
                item.Status == ParseEnum<StudentFollowupStatus>(filter.Status, "invalid_student_followup_status"));

        var items = await query
            .OrderBy(item =>
                item.Status == StudentFollowupStatus.Done || item.Status == StudentFollowupStatus.Cancelled)
            .ThenBy(item => item.DueAt ?? DateTimeOffset.MaxValue)
            .ThenByDescending(item => item.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(item => ToFollowupItem(item))
            .ToArrayAsync(cancellationToken);
        return new CatalogList<TenantAdminStudentFollowupItem>(items);
    }

    public async Task<ContentManagementResult<TenantAdminStudentFollowupItem>> UpsertStudentFollowupAsync(
        TenantAdminActor actor,
        UpsertTenantAdminStudentFollowupCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.Title);
        await AssertStudentAsync(actor, scope, command.StudentUserId, cancellationToken);
        await AssertClassAsync(actor, scope, command.ClassId, cancellationToken);
        await AssertTenantMemberAsync(actor.TenantId, command.AssignedToUserId, cancellationToken);

        TenantStudentFollowup? item = null;
        if (command.Id.HasValue)
        {
            var regionIds = scope.RegionIds.ToArray();
            var classIds = scope.ClassIds.ToArray();
            item = await dbContext.TenantStudentFollowups
                .Where(followup => followup.TenantId == actor.TenantId && followup.Id == command.Id.Value)
                .ApplyDataScope(
                    scope,
                    followup => followup.StudentUserId == actor.UserId ||
                                followup.AssignedToUserId == actor.UserId ||
                                followup.CreatedBy == actor.UserId,
                    followup => (followup.ClassId.HasValue && classIds.Contains(followup.ClassId.Value)) ||
                                dbContext.StudentProfiles.Any(profile =>
                                    profile.TenantId == actor.TenantId &&
                                    profile.UserId == followup.StudentUserId &&
                                    profile.RegionId.HasValue &&
                                    regionIds.Contains(profile.RegionId.Value)) ||
                                dbContext.TenantClassMembers.Any(member =>
                                    member.TenantId == actor.TenantId &&
                                    member.UserId == followup.StudentUserId &&
                                    member.Status == TenantClassMemberStatus.Active &&
                                    classIds.Contains(member.ClassId)))
                .FirstOrDefaultAsync(cancellationToken);
            if (item is null)
                throw new TenantAdminDirectException("Student followup was not found.", "student_followup_not_found");
        }

        var isNew = item is null;
        item ??= new TenantStudentFollowup
        {
            Id = command.Id ?? Guid.NewGuid(),
            TenantId = actor.TenantId,
            StudentUserId = command.StudentUserId,
            CreatedBy = actor.UserId
        };
        item.AssignedToUserId = command.AssignedToUserId;
        item.ClassId = command.ClassId;
        item.Title = command.Title.Trim();
        item.Description = Normalize(command.Description);
        item.FollowupType = ParseEnum(command.FollowupType, StudentFollowupType.Learning,
            "invalid_student_followup_type");
        item.Priority = ParseEnum(command.Priority, StudentFollowupPriority.Normal,
            "invalid_student_followup_priority");
        item.Status = ParseEnum(command.Status, StudentFollowupStatus.Open, "invalid_student_followup_status");
        item.DueAt = command.DueAt;
        item.CompletedAt = item.Status == StudentFollowupStatus.Done ? DateTimeOffset.UtcNow : null;
        item.CompletedBy = item.Status == StudentFollowupStatus.Done ? actor.UserId : null;
        item.Metadata = JsonObjectOrDefault(command.Metadata);
        item.UpdatedBy = actor.UserId;
        if (isNew) dbContext.TenantStudentFollowups.Add(item);

        await AddAuditAsync(actor, "tenant.student_followup.upserted", "tenant_student_followups", item.Id,
            cancellationToken);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<TenantAdminStudentFollowupItem>(ToFollowupItem(item));
    }
}