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

namespace Tiku.Infrastructure.Content;

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

        if (!filter.IncludeInactive) query = query.Where(entry => entry.IsActive);

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

        if (TryParse(filter.EntryType, out ContentEntryType entryType))
            query = query.Where(entry => entry.EntryType == entryType);

        if (!string.IsNullOrWhiteSpace(filter.Keyword))
        {
            var keyword = filter.Keyword.Trim();
            query = query.Where(entry =>
                entry.Name.Contains(keyword) ||
                entry.EntryKey.Contains(keyword) ||
                (entry.Description != null && entry.Description.Contains(keyword)));
        }

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

        return new CatalogList<ContentEntryManagementItem>(items);
    }

    public async Task<ContentManagementResult<ContentEntryManagementItem>> UpsertEntryAsync(
        ContentManagementActor actor,
        UpsertContentEntryCommand command,
        CancellationToken cancellationToken = default)
    {
        var scope = await RequireDataScopeAsync(actor, cancellationToken);
        ArgumentException.ThrowIfNullOrWhiteSpace(command.Name);
        await AssertRegionAsync(actor.TenantId, command.RegionId, cancellationToken);

        var entryKey = Normalize(command.EntryKey) ??
                       Normalize(command.Id?.ToString("N")) ??
                       Guid.NewGuid().ToString("N");
        var entry = await ResolveEntityAsync(
            dbContext.ContentEntries,
            actor.TenantId,
            command.Id,
            item => item.EntryKey == entryKey,
            cancellationToken);

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

        if (entry is not null && !scope.AllowsResource(actor.UserId, entry.CreatedBy, entry.RegionId))
            throw new ContentManagementException("Content entry was not found.", "entry_not_found");

        if (entry is null && !scope.AllowsResource(actor.UserId, actor.UserId, command.RegionId))
            throw new ContentManagementException("Content entry was not found.", "entry_not_found");

        entry ??= new ContentEntry
        {
            Id = command.Id ?? Guid.NewGuid(),
            TenantId = actor.TenantId,
            EntryKey = entryKey,
            CreatedBy = actor.UserId
        };

        entry.RegionId = command.RegionId;
        entry.LegacyId = Normalize(command.LegacyId);
        entry.Name = command.Name.Trim();
        entry.EntryType = Parse(command.EntryType, ContentEntryType.QuestionPractice, "entry_type_invalid");
        entry.Icon = Normalize(command.Icon);
        entry.Route = Normalize(command.Route);
        entry.Description = Normalize(command.Description);
        entry.Visibility = Parse(command.Visibility, ContentVisibility.Public, "visibility_invalid");
        entry.AccessRules = JsonObjectOrDefault(command.AccessRules);
        entry.LayoutConfig = JsonObjectOrDefault(command.LayoutConfig);
        entry.SortOrder = command.Order ?? 0;
        entry.IsActive = command.IsActive ?? true;
        entry.UpdatedBy = actor.UserId;

        if (isNew) dbContext.ContentEntries.Add(entry);

        await dbContext.SaveChangesAsync(cancellationToken);
        return new ContentManagementResult<ContentEntryManagementItem>(ToEntryItem(entry));
    }
}