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

namespace Tiku.Infrastructure.QuestionBanks;

public sealed class QuestionReferenceService(
    TikuDbContext dbContext,
    IPublicQuestionAccessPolicy accessPolicy,
    ITenantExecutionScope tenantExecutionScope) : IQuestionReferenceService
{
    public async Task<TenantQuestionReference> ResolveAsync(
        Guid tenantId,
        Guid? userId,
        QuestionLocator locator,
        CancellationToken cancellationToken = default)
    {
        var ownerTenantId = locator.Source switch
        {
            QuestionSource.Tenant => await ResolveTenantQuestionAsync(tenantId, locator.QuestionId, cancellationToken),
            QuestionSource.Platform => await ResolvePlatformQuestionAsync(tenantId, locator.QuestionId, cancellationToken),
            _ => throw new QuestionLocatorException("question_source_invalid", "Question source is invalid.")
        };

        var existing = await dbContext.TenantQuestionReferences.SingleOrDefaultAsync(
            reference =>
                reference.TenantId == tenantId &&
                reference.QuestionOwnerTenantId == ownerTenantId &&
                reference.QuestionId == locator.QuestionId,
            cancellationToken);
        if (existing is not null)
        {
            return existing;
        }

        var reference = new TenantQuestionReference
        {
            TenantId = tenantId,
            QuestionOwnerTenantId = ownerTenantId,
            QuestionId = locator.QuestionId,
            Source = locator.Source,
            CreatedBy = userId
        };
        dbContext.TenantQuestionReferences.Add(reference);
        return reference;
    }

    private async Task<Guid> ResolveTenantQuestionAsync(
        Guid tenantId,
        Guid questionId,
        CancellationToken cancellationToken)
    {
        var exists = await dbContext.Questions.AsNoTracking().AnyAsync(
            question =>
                question.TenantId == tenantId &&
                question.Id == questionId &&
                question.Status == QuestionStatus.Published,
            cancellationToken);
        return exists
            ? tenantId
            : throw new QuestionLocatorException("question_not_found", "Tenant question was not found.");
    }

    private async Task<Guid> ResolvePlatformQuestionAsync(
        Guid tenantId,
        Guid questionId,
        CancellationToken cancellationToken)
    {
        await accessPolicy.EnsureCanStartAsync(tenantId, cancellationToken);
        var ownerTenantId = await tenantExecutionScope.ExecuteAsync(
            new SystemScopeRequest(
                tenantId, SystemScopeCallerType.PublicQuestionBank, nameof(QuestionReferenceService),
                "Resolve a platform question for an entitled tenant", Guid.NewGuid().ToString("N")),
            async (provider, token) =>
            {
                var systemDbContext = provider.GetRequiredService<TikuDbContext>();
                return await systemDbContext.Questions.AsNoTracking()
                    .Where(question =>
                        question.Id == questionId &&
                        question.Status == QuestionStatus.Published)
                    .Join(
                        systemDbContext.Tenants.AsNoTracking().Where(tenant => tenant.Mode == TenantMode.PlatformOwned),
                        question => question.TenantId,
                        tenant => tenant.Id,
                        (question, tenant) => (Guid?)tenant.Id)
                    .SingleOrDefaultAsync(token);
            },
            cancellationToken);

        return ownerTenantId ?? throw new QuestionLocatorException(
            "question_not_found",
            "Platform question was not found.");
    }
}
