using JobTrackerApi.Controllers;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class StrategySnapshotOperationTests
{
    [Fact]
    public async Task Producer_returns_202_and_duplicate_click_reuses_the_operation()
    {
        await using var fixture = await Fixture.CreateAsync();
        await fixture.SeedAsync();

        await using var scope = fixture.Provider.CreateAsyncScope();
        using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-1");
        var controller = Controller(scope);
        var first = Assert.IsType<AcceptedResult>(await controller.Enqueue(1, new StrategySnapshotRequest(null), default));
        var duplicate = Assert.IsType<AcceptedResult>(await controller.Enqueue(1, new StrategySnapshotRequest(null), default));
        var firstBody = Assert.IsType<StrategySnapshotOperationResponse>(first.Value);
        var duplicateBody = Assert.IsType<StrategySnapshotOperationResponse>(duplicate.Value);

        Assert.True(firstBody.Created);
        Assert.False(duplicateBody.Created);
        Assert.Equal(firstBody.Operation.Id, duplicateBody.Operation.Id);
        Assert.Equal(OperationStatuses.Queued, firstBody.Operation.Status);
        Assert.Single(await scope.ServiceProvider.GetRequiredService<JobTrackerContext>().UserOperations.ToListAsync());
    }

    [Fact]
    public async Task Worker_persists_one_result_and_provider_metadata_then_GET_is_read_only()
    {
        await using var fixture = await Fixture.CreateAsync();
        await fixture.SeedAsync();
        await fixture.EnqueueAsync();

        Assert.True(await fixture.Provider.GetRequiredService<AiOperationWorker>().RunOnceAsync(default));
        Assert.False(await fixture.Provider.GetRequiredService<AiOperationWorker>().RunOnceAsync(default));

        await using var scope = fixture.Provider.CreateAsyncScope();
        using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-1");
        var controller = Controller(scope);
        var result = Assert.IsType<OkObjectResult>((await controller.Get(1, null, default)).Result);
        Assert.Equal("Lead with delivery evidence.", Assert.IsType<FocusPlanDto>(result.Value).StrategicSummary);

        var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
        Assert.Single(await db.AiWorkspaceNotes.ToListAsync());
        var operation = Assert.Single(await db.UserOperations.ToListAsync());
        Assert.Equal(OperationStatuses.Succeeded, operation.Status);
        Assert.Equal("ollama", operation.Provider);
        Assert.Equal("qwen-test", operation.Model);
        Assert.Equal(1, fixture.GenerationCalls);
        var usage = Assert.Single(await db.AiUsageRecords.ToListAsync());
        Assert.Equal("strategy.snapshot", usage.TaskType);
        Assert.True(usage.InputCharacterCount > 0);
        Assert.Equal(Fixture.ValidResponse.Length, usage.OutputCharacterCount);
        Assert.Equal((usage.InputCharacterCount + usage.OutputCharacterCount + 3) / 4, usage.EstimatedTokenCount);
        Assert.True(usage.EstimatedTokenCount < AiUsageMeter.ReservationFor(StrategySnapshotService.TaskType).EstimatedTokens);
    }

    [Fact]
    public async Task Invalid_provider_shape_is_retryable_and_does_not_publish_partial_output()
    {
        await using var fixture = await Fixture.CreateAsync("not-json");
        await fixture.SeedAsync();
        await fixture.EnqueueAsync();

        Assert.True(await fixture.Provider.GetRequiredService<AiOperationWorker>().RunOnceAsync(default));

        await using var scope = fixture.Provider.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
        var operation = await db.UserOperations.IgnoreQueryFilters().SingleAsync();
        Assert.Equal(OperationStatuses.WaitingForRetry, operation.Status);
        Assert.Equal("invalid_provider_response", operation.FailureCategory);
        Assert.Empty(await db.AiWorkspaceNotes.IgnoreQueryFilters().ToListAsync());
    }

    [Fact]
    public async Task Latest_operation_and_result_are_tenant_scoped()
    {
        await using var fixture = await Fixture.CreateAsync();
        await fixture.SeedAsync();
        await fixture.SeedUserAsync("user-2", pro: true);
        await fixture.EnqueueAsync();

        await using var scope = fixture.Provider.CreateAsyncScope();
        using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-2");
        var controller = Controller(scope);
        Assert.IsType<NotFoundResult>((await controller.LatestOperation(1, null, default)).Result);
        Assert.IsType<NotFoundObjectResult>((await controller.Get(1, null, default)).Result);
    }

    private static StrategySnapshotController Controller(AsyncServiceScope scope)
    {
        var controller = ActivatorUtilities.CreateInstance<StrategySnapshotController>(scope.ServiceProvider);
        controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() };
        return controller;
    }

    private sealed class Fixture : IAsyncDisposable
    {
        internal const string ValidResponse = """{"strategicSummary":"Lead with delivery evidence.","cvBulletIdeas":["Built APIs"],"proofPointsToLeadWith":["Led delivery"],"coverLetterAngles":["Platform relevance"]}""";
        private readonly SqliteConnection _connection;
        private readonly Mock<ISummarizerService> _summarizer;
        public ServiceProvider Provider { get; }
        public int GenerationCalls { get; private set; }

        private Fixture(SqliteConnection connection, ServiceProvider provider, Mock<ISummarizerService> summarizer)
        {
            _connection = connection;
            Provider = provider;
            _summarizer = summarizer;
        }

        public static async Task<Fixture> CreateAsync(string response = ValidResponse)
        {
            var connection = new SqliteConnection("Data Source=:memory:");
            await connection.OpenAsync();
            Fixture? fixture = null;
            var summarizer = new Mock<ISummarizerService>();
            summarizer.Setup(item => item.GenerateSectionWithMetadataAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(() =>
                {
                    fixture!.GenerationCalls++;
                    return new AiGenerationResult(response, "ollama", "qwen-test", RouteReason: "local_primary");
                });
            var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["AiQueue:HeartbeatSeconds"] = "5",
                ["AiQueue:MaxAttempts"] = "3",
                ["Ai:ExternalProcessingEnabled"] = "false",
            }).Build();
            var services = new ServiceCollection();
            services.AddSingleton<IConfiguration>(configuration);
            services.AddLogging();
            services.AddHttpContextAccessor();
            services.AddScoped<CurrentUserService>();
            services.AddScoped<ICurrentUserService>(provider => provider.GetRequiredService<CurrentUserService>());
            services.AddDbContext<JobTrackerContext>((_, options) => options.UseSqlite(connection));
            services.AddIdentityCore<ApplicationUser>().AddRoles<IdentityRole>().AddEntityFrameworkStores<JobTrackerContext>();
            services.AddSingleton(TimeProvider.System);
            services.AddSingleton<AiPrivacyPolicy>();
            services.AddSingleton<AiOperationExecutionScope>();
            services.AddScoped<UserOperationStore>();
            services.AddScoped<AiUsageMeter>();
            services.AddScoped<AiOperationAdmission>();
            services.AddScoped<StrategySnapshotService>();
            services.AddSingleton<IAiOperationHandler, StrategySnapshotOperationHandler>();
            services.AddSingleton<AiOperationWorker>();
            services.AddSingleton(summarizer.Object);
            var provider = services.BuildServiceProvider();
            fixture = new Fixture(connection, provider, summarizer);
            await using var scope = provider.CreateAsyncScope();
            await scope.ServiceProvider.GetRequiredService<JobTrackerContext>().Database.EnsureCreatedAsync();
            return fixture;
        }

        public async Task SeedAsync()
        {
            await SeedUserAsync("user-1", pro: true);
            await using var scope = Provider.CreateAsyncScope();
            using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-1");
            var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
            var company = new Company { Id = 1, Name = "Acme", OwnerUserId = "user-1" };
            db.Companies.Add(company);
            db.JobApplications.Add(new JobApplication { Id = 1, CompanyId = 1, Company = company, JobTitle = "Backend Developer", Description = "Needs .NET and SQL.", OwnerUserId = "user-1" });
            await db.SaveChangesAsync();
        }

        public async Task SeedUserAsync(string userId, bool pro)
        {
            await using var scope = Provider.CreateAsyncScope();
            var roles = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            if (pro && !await roles.RoleExistsAsync("Premium")) Assert.True((await roles.CreateAsync(new IdentityRole("Premium"))).Succeeded);
            var users = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
            var user = new ApplicationUser { Id = userId, UserName = $"{userId}@example.test", Email = $"{userId}@example.test", EmailConfirmed = true, AiEnabled = true, ProfileCvText = "Built .NET APIs and led delivery." };
            Assert.True((await users.CreateAsync(user)).Succeeded);
            if (pro) Assert.True((await users.AddToRoleAsync(user, "Premium")).Succeeded);
        }

        public async Task EnqueueAsync()
        {
            await using var scope = Provider.CreateAsyncScope();
            using var owner = scope.ServiceProvider.GetRequiredService<CurrentUserService>().UseBackgroundUser("user-1");
            var controller = Controller(scope);
            Assert.IsType<AcceptedResult>(await controller.Enqueue(1, new StrategySnapshotRequest(null), default));
        }

        public async ValueTask DisposeAsync()
        {
            _summarizer.Reset();
            await Provider.DisposeAsync();
            await _connection.DisposeAsync();
        }
    }
}
