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

namespace Tiku.Infrastructure.Content;

public sealed partial class ContentManagementService
{
    public async Task<CatalogList<QuestionCollectionManagementItem>> GetCollectionsAsync(
        ContentManagementActor actor,
        ContentManagementFilter filter,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        var regionIds = scope.RegionIds.ToArray();
        var query = dbContext.QuestionCollections
            .AsNoTracking()
            .Where(collection => collection.TenantId == actor.TenantId)
            .ApplyDataScope(
                scope,
                collection => collection.CreatedBy == actor.UserId,
                collection => collection.RegionId.HasValue && regionIds.Contains(collection.RegionId.Value));

        if (!filter.IncludeInactive) query = query.Where(collection => collection.Status == ContentStatus.Active);

        if (filter.RegionId.HasValue) query = query.Where(collection => collection.RegionId == filter.RegionId.Value);

        if (filter.EntryId.HasValue) query = query.Where(collection => collection.EntryId == filter.EntryId.Value);

        if (filter.NodeId.HasValue) query = query.Where(collection => collection.NodeId == filter.NodeId.Value);

        if (TryParse(filter.CollectionType, out QuestionCollectionType collectionType))
            query = query.Where(collection => collection.CollectionType == collectionType);

        if (!string.IsNullOrWhiteSpace(filter.Keyword))
        {
            var keyword = filter.Keyword.Trim();
            query = query.Where(collection => collection.Name.Contains(keyword));
        }

        var items = await query
            .OrderBy(collection => collection.SortOrder)
            .ThenBy(collection => collection.CreatedAt)
            .Take(ResolveLimit(filter.Limit))
            .Select(collection => ToCollectionItem(collection))
            .ToArrayAsync(cancellationToken);

        return new CatalogList<QuestionCollectionManagementItem>(items);
    }

    public async Task<ContentManagementResult<QuestionCollectionManagementItem>> UpsertCollectionAsync(
        ContentManagementActor actor,
        UpsertQuestionCollectionCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
        await AssertRegionAsync(actor.TenantId, command.RegionId, cancellationToken);
        await AssertEntryAsync(actor, scope, command.EntryId, cancellationToken);
        await AssertNodeAsync(actor, scope, command.NodeId, cancellationToken);
        await AssertReferenceAsync<Subject>(actor.TenantId, command.SubjectId, "subject_not_found", cancellationToken);
        await AssertReferenceAsync<Category>(actor.TenantId, command.CategoryId, "category_not_found",
            cancellationToken);
        await AssertReferenceAsync<QuestionBank>(actor.TenantId, command.QuestionBankId, "question_bank_not_found",
            cancellationToken);

        var collection = await ResolveEntityByIdOrLegacyAsync(
            dbContext.QuestionCollections,
            actor.TenantId,
            command.Id,
            command.LegacyId,
            cancellationToken);

        var isNew = collection is null;
        if (command.Id.HasValue && (collection is null || collection.Id != command.Id.Value))
            throw new ContentManagementException("Collection was not found.", "collection_not_found");

        if (collection is not null && !scope.AllowsResource(actor.UserId, collection.CreatedBy, collection.RegionId))
            throw new ContentManagementException("Collection was not found.", "collection_not_found");

        if (collection is null && !scope.AllowsResource(actor.UserId, actor.UserId, command.RegionId))
            throw new ContentManagementException("Collection was not found.", "collection_not_found");

        collection ??= new QuestionCollection
        {
            Id = command.Id ?? Guid.NewGuid(),
            TenantId = actor.TenantId,
            CreatedBy = actor.UserId
        };

        collection.RegionId = command.RegionId;
        collection.EntryId = command.EntryId;
        collection.NodeId = command.NodeId;
        collection.SubjectId = command.SubjectId;
        collection.CategoryId = command.CategoryId;
        collection.QuestionBankId = command.QuestionBankId;
        collection.LegacyId = Normalize(command.LegacyId);
        collection.Name = command.Name.Trim();
        collection.CollectionType =
            Parse(command.CollectionType, QuestionCollectionType.Dynamic, "collection_type_invalid");
        collection.SourceType = Parse(command.SourceType, QuestionCollectionSourceType.Filters,
            "collection_source_type_invalid");
        collection.Filters = JsonObjectOrDefault(command.Filters);
        collection.TotalScore = command.TotalScore;
        collection.DurationMinutes = command.DurationMinutes;
        collection.Status = Parse(command.Status, ContentStatus.Active, "content_status_invalid");
        collection.SortOrder = command.Order ?? 0;
        collection.AccessRules = JsonObjectOrDefault(command.AccessRules);
        collection.Metadata = JsonObjectOrDefault(command.Metadata);
        collection.UpdatedBy = actor.UserId;

        if (isNew) dbContext.QuestionCollections.Add(collection);

        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<QuestionCollectionManagementItem>(ToCollectionItem(collection));
    }

    public async Task<CollectionItemsReplaceResult> ReplaceCollectionItemsAsync(
        ContentManagementActor actor,
        ReplaceCollectionItemsCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        var regionIds = scope.RegionIds.ToArray();
        var collection = await dbContext.QuestionCollections
            .Where(item => item.TenantId == actor.TenantId && item.Id == command.CollectionId)
            .ApplyDataScope(
                scope,
                item => item.CreatedBy == actor.UserId,
                item => item.RegionId.HasValue && regionIds.Contains(item.RegionId.Value))
            .SingleOrDefaultAsync(cancellationToken);

        if (collection is null)
            throw new ContentManagementException("Collection was not found.", "collection_not_found");

        var resolvedQuestions = new List<(CollectionQuestionCommand Command, TenantQuestionReference Reference)>();
        foreach (var question in command.Questions)
        {
            var reference = await questionReferenceService.ResolveAsync(
                actor.TenantId,
                actor.UserId,
                question.Locator,
                cancellationToken);
            resolvedQuestions.Add((question, reference));
        }

        var oldItems = await dbContext.QuestionCollectionItems
            .Where(item => item.TenantId == actor.TenantId && item.CollectionId == command.CollectionId)
            .ToArrayAsync(cancellationToken);
        dbContext.QuestionCollectionItems.RemoveRange(oldItems);

        var items = resolvedQuestions
            .Select((resolved, index) => new QuestionCollectionItem
            {
                TenantId = actor.TenantId,
                CollectionId = command.CollectionId,
                QuestionReferenceId = resolved.Reference.Id,
                QuestionOwnerTenantId = resolved.Reference.QuestionOwnerTenantId,
                QuestionId = resolved.Reference.QuestionId,
                SectionKey = Normalize(resolved.Command.SectionKey),
                SortOrder = resolved.Command.Order ?? index,
                Score = resolved.Command.Score,
                Required = resolved.Command.Required ?? true,
                Metadata = JsonObjectOrDefault(resolved.Command.Metadata)
            })
            .ToArray();

        dbContext.QuestionCollectionItems.AddRange(items);
        collection.QuestionCount = items.Length;
        collection.UpdatedBy = actor.UserId;
        await dbContext.SaveChangesAsync(cancellationToken);

        return new CollectionItemsReplaceResult(
            command.CollectionId,
            collection.QuestionCount,
            items.Select(ToCollectionItemItem).ToArray());
    }
}