using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Identity;

namespace JobTrackerApi.Services;

// The shared synchronous provider boundary. Durable operations and the AI workspace reserve
// usage before reaching this layer, so their execution scopes explicitly bypass this decorator.
public sealed class MeteredSummarizerService(
    SummarizerService inner,
    JobTrackerContext db,
    UserManager<ApplicationUser> users,
    AiUsageMeter usage,
    AiOperationExecutionScope operationScope,
    AiUsageExecutionScope usageScope) : ISummarizerService
{
    public Task<string?> SummarizeAsync(string text, int maxLength = 150, int minLength = 30)
        => MeterAsync("synchronous.summary", text, maxLength, ct => inner.SummarizeAsync(text, maxLength, minLength), CancellationToken.None);

    public Task<string?> SummarizeSectionAsync(string instruction, string text, int maxLength = 180, int minLength = 40)
        => MeterAsync("synchronous.rewrite", instruction + "\n\n" + text, maxLength,
            ct => inner.SummarizeSectionAsync(instruction, text, maxLength, minLength), CancellationToken.None);

    public Task<AiGenerationResult?> GenerateSectionWithMetadataAsync(
        string instruction,
        string text,
        int maxLength = 180,
        int minLength = 40,
        CancellationToken cancellationToken = default)
        => MeterAsync("synchronous.generate", instruction + "\n\n" + text, maxLength,
            ct => inner.GenerateSectionWithMetadataAsync(instruction, text, maxLength, minLength, ct), cancellationToken);

    public Task<AiTextExtractionResult?> ExtractTextAsync(
        Stream stream,
        string fileName,
        string? contentType = null,
        CancellationToken cancellationToken = default)
        => inner.ExtractTextAsync(stream, fileName, contentType, cancellationToken);

    public Task RunProbeAsync(CancellationToken cancellationToken = default)
        => inner.RunProbeAsync(cancellationToken);

    public Task<AiServiceMetrics> GetMetricsAsync(CancellationToken cancellationToken = default)
        => inner.GetMetricsAsync(cancellationToken);

    private async Task<T?> MeterAsync<T>(
        string taskType,
        string input,
        int maximumOutputCharacters,
        Func<CancellationToken, Task<T?>> generate,
        CancellationToken cancellationToken)
        where T : class
    {
        if (usageScope.IsSuppressed || operationScope.Current is not null || string.IsNullOrWhiteSpace(db.CurrentUserId))
            return await generate(cancellationToken);

        var ownerUserId = db.CurrentUserId;
        var user = await users.FindByIdAsync(ownerUserId);
        var entitlements = user is null ? AccountPlans.ForRoles(null) : AccountPlans.ForRoles(await users.GetRolesAsync(user));
        if (user is null || !user.AiEnabled || !entitlements.Ai)
            throw new AiUsageLimitException("ai_not_available", "AI features require an active Pro account with AI enabled.");

        var boundedInputCharacters = Math.Min(input.Length, 20_000);
        var boundedOutputCharacters = Math.Clamp(maximumOutputCharacters, 0, 4_096);
        var estimatedTokens = Math.Max(1, (boundedInputCharacters + boundedOutputCharacters + 3) / 4);
        var reservation = await usage.ReserveAsync(
            ownerUserId,
            entitlements,
            "synchronous",
            Guid.NewGuid().ToString("D"),
            taskType,
            boundedInputCharacters,
            estimatedTokens,
            cancellationToken);

        var result = await generate(cancellationToken);
        switch (result)
        {
            case string text:
                await usage.FinalizeAsync(reservation.Record.Id, boundedInputCharacters, text.Length, cancellationToken);
                break;
            case AiGenerationResult generation:
                await usage.FinalizeAsync(reservation.Record.Id, boundedInputCharacters, generation.Text.Length, cancellationToken);
                break;
        }

        return result;
    }
}
