using System.Security.Claims;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class AiPrivacyPolicyTests
{
    [Theory]
    [InlineData(false, true, true, "local")]
    [InlineData(true, false, true, "local")]
    [InlineData(true, true, false, "local")]
    [InlineData(true, true, true, "gemini")]
    public async Task External_processing_requires_admin_gate_user_consent_and_pro(
        bool adminEnabled,
        bool userAllowed,
        bool pro,
        string expectedProvider)
    {
        await using var fixture = await Fixture.CreateAsync(adminEnabled);
        await fixture.CreateUserAsync(userAllowed, pro);

        var decision = await fixture.Policy.EvaluateAsync("user-1");

        Assert.Equal(expectedProvider != "local", decision.ExternalProcessingAllowed);
        Assert.Equal(expectedProvider, decision.Provider);
    }

    [Fact]
    public async Task Disabled_ai_always_forces_local_processing()
    {
        await using var fixture = await Fixture.CreateAsync(adminEnabled: true);
        await fixture.CreateUserAsync(externalAllowed: true, pro: true, aiEnabled: false);

        var decision = await fixture.Policy.EvaluateAsync("user-1");

        Assert.False(decision.ExternalProcessingAllowed);
        Assert.Equal("local", decision.Provider);
    }

    [Theory]
    [InlineData("local_only")]
    [InlineData("unexpected")]
    public async Task Local_only_or_invalid_admin_mode_disables_external_processing(string routingMode)
    {
        await using var fixture = await Fixture.CreateAsync(adminEnabled: true, routingMode: routingMode);
        await fixture.CreateUserAsync(externalAllowed: true, pro: true);

        var decision = await fixture.Policy.EvaluateAsync("user-1");

        Assert.False(decision.ExternalProcessingAllowed);
        Assert.Equal("local", decision.Provider);
    }

    [Fact]
    public async Task Cv_request_carries_external_permission_only_after_the_policy_allows_it()
    {
        await using var fixture = await Fixture.CreateAsync(adminEnabled: true);
        await fixture.CreateUserAsync(externalAllowed: true, pro: true);
        var context = new DefaultHttpContext
        {
            User = new ClaimsPrincipal(new ClaimsIdentity(
                new[] { new Claim(ClaimTypes.NameIdentifier, "user-1") }, "local")),
        };
        var capture = new CaptureHandler();
        var handler = new AiPrivacyHeaderHandler(
            new HttpContextAccessor { HttpContext = context },
            fixture.Policy,
            new AiOperationExecutionScope())
        {
            InnerHandler = capture,
        };

        using var client = new HttpClient(handler);
        await client.PostAsync("http://ai-service/cv/rewrite", new StringContent("{}"));

        Assert.Equal("true", capture.ExternalAllowed);
    }

    [Fact]
    public async Task Background_operation_carries_its_rechecked_policy_and_task_without_http_user_context()
    {
        await using var fixture = await Fixture.CreateAsync(adminEnabled: true);
        var executionScope = new AiOperationExecutionScope();
        var capture = new CaptureHandler();
        var handler = new AiPrivacyHeaderHandler(new HttpContextAccessor(), fixture.Policy, executionScope)
        {
            InnerHandler = capture,
        };
        var lease = new UserOperationLease(Guid.NewGuid(), "user-1", "lease", "strategy.snapshot",
            "external_allowed", "job", "42", 1, DateTime.UtcNow.AddMinutes(5));

        using var routing = executionScope.Use(new AiOperationExecutionContext(lease, "external_allowed"));
        using var client = new HttpClient(handler);
        await client.PostAsync("http://ai-service/cv/rewrite", new StringContent("{}"));

        Assert.Equal("true", capture.ExternalAllowed);
        Assert.Equal("strategy.snapshot", capture.TaskType);
    }

    private sealed class CaptureHandler : HttpMessageHandler
    {
        public string? ExternalAllowed { get; private set; }
        public string? TaskType { get; private set; }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            ExternalAllowed = request.Headers.TryGetValues(AiPrivacyPolicy.ExternalAllowedHeader, out var values)
                ? values.Single()
                : null;
            TaskType = request.Headers.TryGetValues(AiPrivacyPolicy.TaskTypeHeader, out var taskValues)
                ? taskValues.Single()
                : null;
            return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK));
        }
    }

    private sealed class Fixture : IAsyncDisposable
    {
        private readonly SqliteConnection _connection;
        private readonly ServiceProvider _services;
        public AiPrivacyPolicy Policy { get; }

        private Fixture(SqliteConnection connection, ServiceProvider services, AiPrivacyPolicy policy)
        {
            _connection = connection;
            _services = services;
            Policy = policy;
        }

        public static async Task<Fixture> CreateAsync(bool adminEnabled, string routingMode = "local_first")
        {
            var connection = new SqliteConnection("Data Source=:memory:");
            await connection.OpenAsync();
            var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Ai:ExternalProcessingEnabled"] = adminEnabled.ToString(),
                ["Ai:ExternalProvider"] = "gemini",
                ["Ai:RoutingMode"] = routingMode,
            }).Build();
            var services = new ServiceCollection();
            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>();
            var provider = services.BuildServiceProvider();
            await using var scope = provider.CreateAsyncScope();
            await scope.ServiceProvider.GetRequiredService<JobTrackerContext>().Database.EnsureCreatedAsync();
            return new Fixture(connection, provider, new AiPrivacyPolicy(configuration, provider.GetRequiredService<IServiceScopeFactory>()));
        }

        public async Task CreateUserAsync(bool externalAllowed, bool pro, bool aiEnabled = true)
        {
            await using var scope = _services.CreateAsyncScope();
            var roles = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            if (pro) await roles.CreateAsync(new IdentityRole("Premium"));
            var users = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
            var user = new ApplicationUser
            {
                Id = "user-1",
                UserName = "synthetic@example.test",
                Email = "synthetic@example.test",
                AiEnabled = aiEnabled,
                ExternalAiProcessingAllowed = externalAllowed,
            };
            Assert.True((await users.CreateAsync(user)).Succeeded);
            if (pro) Assert.True((await users.AddToRoleAsync(user, "Premium")).Succeeded);
        }

        public async ValueTask DisposeAsync()
        {
            await _services.DisposeAsync();
            await _connection.DisposeAsync();
        }
    }
}
