using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Catalog;
using Tiku.Application.QuestionBanks;
using Tiku.Application.Security;
using Tiku.Domain.Catalog;
using Tiku.Domain.Content;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.Catalog;

public sealed class TaxonomyService(
    TikuDbContext dbContext,
    IPublicQuestionAccessPolicy accessPolicy,
    ITenantExecutionScope tenantExecutionScope) : ITaxonomyService
{
    public async Task<IReadOnlyCollection<TaxonomyNodeItem>> ListAsync(
        Guid tenantId,
        CancellationToken cancellationToken = default)
    {
        var includePlatform = true;
        try
        {
            await accessPolicy.EnsureCanStartAsync(tenantId, cancellationToken);
        }
        catch (PublicQuestionAccessDeniedException)
        {
            includePlatform = false;
        }

        return await tenantExecutionScope.ExecuteAsync(
            new SystemScopeRequest(
                tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(TaxonomyService),
                "List platform taxonomy with tenant extensions", Guid.NewGuid().ToString("N")),
            async (provider, token) =>
            {
                var systemDbContext = provider.GetRequiredService<TikuDbContext>();
                var platformTenantId = includePlatform
                    ? await systemDbContext.Tenants.AsNoTracking()
                        .Where(tenant => tenant.Mode == TenantMode.PlatformOwned)
                        .Select(tenant => (Guid?)tenant.Id)
                        .SingleOrDefaultAsync(token)
                    : null;
                return await systemDbContext.TaxonomyNodes.AsNoTracking()
                    .Where(node =>
                        node.IsActive &&
                        (node.TenantId == tenantId ||
                         (platformTenantId.HasValue && node.TenantId == platformTenantId.Value)))
                    .OrderBy(node => node.Depth)
                    .ThenBy(node => node.SortOrder)
                    .Select(node => new TaxonomyNodeItem(
                        node.Id,
                        node.TenantId == tenantId ? QuestionSource.Tenant : QuestionSource.Platform,
                        node.ParentId,
                        !node.ParentOwnerTenantId.HasValue
                            ? null
                            : node.ParentOwnerTenantId == tenantId
                                ? QuestionSource.Tenant
                                : QuestionSource.Platform,
                        node.NodeType,
                        node.Code,
                        node.Name,
                        node.Path,
                        node.Depth,
                        node.SortOrder,
                        node.Metadata))
                    .ToArrayAsync(token);
            },
            cancellationToken);
    }

    public async Task<TaxonomyNodeItem> CreateAsync(
        Guid tenantId,
        CreateTaxonomyNodeCommand command,
        CancellationToken cancellationToken = default)
    {
        Guid? parentOwnerTenantId = null;
        TaxonomyParent? parent = null;
        if (command.ParentId.HasValue)
        {
            parentOwnerTenantId = command.ParentSource switch
            {
                QuestionSource.Tenant => tenantId,
                QuestionSource.Platform => await ResolvePlatformTenantIdAsync(tenantId, cancellationToken),
                _ => throw new InvalidOperationException("A parent source is required when parentId is provided.")
            };
            parent = await tenantExecutionScope.ExecuteAsync(
                new SystemScopeRequest(
                    tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(TaxonomyService),
                    "Validate taxonomy extension parent ownership", Guid.NewGuid().ToString("N")),
                async (provider, token) =>
                {
                    var systemDbContext = provider.GetRequiredService<TikuDbContext>();
                    return await systemDbContext.TaxonomyNodes.AsNoTracking()
                        .Where(node =>
                            node.TenantId == parentOwnerTenantId &&
                            node.Id == command.ParentId.Value &&
                            node.IsActive)
                        .Select(node => new TaxonomyParent(node.Path, node.Depth))
                        .SingleOrDefaultAsync(token);
                },
                cancellationToken)
                ?? throw new InvalidOperationException("Taxonomy parent was not found.");
        }

        var node = new TaxonomyNode
        {
            TenantId = tenantId,
            ParentOwnerTenantId = parentOwnerTenantId,
            ParentId = command.ParentId,
            NodeType = command.NodeType,
            Code = command.Code.Trim(),
            Name = command.Name.Trim(),
            Depth = parent is null ? 0 : parent.Depth + 1,
            SortOrder = command.SortOrder,
            Metadata = command.Metadata.Clone()
        };
        node.Path = parent is null
            ? $"n{node.Id:N}"
            : $"{parent.Path}.n{node.Id:N}";
        dbContext.TaxonomyNodes.Add(node);
        await dbContext.SaveChangesAsync(cancellationToken);
        return new TaxonomyNodeItem(
            node.Id,
            QuestionSource.Tenant,
            node.ParentId,
            node.ParentId.HasValue ? command.ParentSource : null,
            node.NodeType,
            node.Code,
            node.Name,
            node.Path,
            node.Depth,
            node.SortOrder,
            node.Metadata);
    }

    private async Task<Guid> ResolvePlatformTenantIdAsync(Guid tenantId, CancellationToken cancellationToken)
    {
        await accessPolicy.EnsureCanStartAsync(tenantId, cancellationToken);
        return await tenantExecutionScope.ExecuteAsync(
            new SystemScopeRequest(
                tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(TaxonomyService),
                "Resolve platform taxonomy owner", Guid.NewGuid().ToString("N")),
            async (provider, token) => await provider.GetRequiredService<TikuDbContext>()
                .Tenants.AsNoTracking()
                .Where(tenant => tenant.Mode == TenantMode.PlatformOwned)
                .Select(tenant => tenant.Id)
                .SingleAsync(token),
            cancellationToken);
    }

    private sealed record TaxonomyParent(string? Path, int Depth);
}
