using System.Security.Claims;
using JobTrackerApi.Controllers;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class ProEntitlementAuthorizationTests
{
    [Theory]
    [InlineData("Premium")]
    [InlineData("Admin")]
    public async Task Current_pro_or_admin_role_satisfies_policy(string role)
    {
        var (handler, user) = Handler(role);
        var context = Context(user.Id, includeStalePremiumClaim: false);

        await handler.HandleAsync(context);

        Assert.True(context.HasSucceeded);
    }

    [Fact]
    public async Task Free_user_fails_even_when_session_contains_a_stale_premium_claim()
    {
        var (handler, user) = Handler();
        var context = Context(user.Id, includeStalePremiumClaim: true);

        await handler.HandleAsync(context);

        Assert.False(context.HasSucceeded);
    }

    [Fact]
    public async Task Pro_user_with_ai_disabled_fails_with_the_privacy_code()
    {
        var (handler, user) = Handler("Premium");
        user.AiEnabled = false;
        var context = Context(user.Id, includeStalePremiumClaim: false);

        await handler.HandleAsync(context);

        Assert.False(context.HasSucceeded);
        Assert.Contains(context.FailureReasons, reason => reason.Message == ProEntitlement.DisabledCode);
    }

    [Fact]
    public async Task Policy_failure_returns_the_stable_pro_required_contract()
    {
        var http = new DefaultHttpContext();
        http.Response.Body = new MemoryStream();
        var requirement = new ProEntitlementRequirement();
        var failure = AuthorizationFailure.Failed(new[] { requirement });
        var result = PolicyAuthorizationResult.Forbid(failure);

        await new ProEntitlementAuthorizationResultHandler().HandleAsync(
            _ => Task.CompletedTask,
            http,
            new AuthorizationPolicy(new[] { requirement }, Array.Empty<string>()),
            result);

        http.Response.Body.Position = 0;
        var body = await new StreamReader(http.Response.Body).ReadToEndAsync();
        Assert.Equal(StatusCodes.Status403Forbidden, http.Response.StatusCode);
        Assert.Contains("\"code\":\"pro_required\"", body);
        Assert.DoesNotContain("Premium", body, StringComparison.OrdinalIgnoreCase);
    }

    [Theory]
    [InlineData(typeof(AiWorkspaceController), nameof(AiWorkspaceController.Generate))]
    [InlineData(typeof(CvVariantController), nameof(CvVariantController.AiAssist))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.Upload))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.Reprocess))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.Rebuild))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.RewriteSection))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.BuildRewritePreview))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.ExportProfileCvPdf))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.Parse))]
    [InlineData(typeof(ProfileCvController), nameof(ProfileCvController.Improve))]
    [InlineData(typeof(JobApplicationsController), nameof(JobApplicationsController.RefreshAi))]
    [InlineData(typeof(JobApplicationsController), nameof(JobApplicationsController.GetCandidateFit))]
    [InlineData(typeof(StrategySnapshotController), nameof(StrategySnapshotController.Enqueue))]
    [InlineData(typeof(JobApplicationsController), nameof(JobApplicationsController.GetInterviewPrep))]
    [InlineData(typeof(JobApplicationsController), nameof(JobApplicationsController.GenerateTailoredCvDraft))]
    [InlineData(typeof(JobApplicationsController), nameof(JobApplicationsController.GenerateApplicationPackage))]
    [InlineData(typeof(JobApplicationsController), nameof(JobApplicationsController.GetFollowUpDraft))]
    public void Explicit_ai_action_requires_the_pro_policy(Type controller, string action)
    {
        var method = controller.GetMethod(action);
        Assert.NotNull(method);
        var attributes = method!.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast<AuthorizeAttribute>()
            .Concat(controller.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast<AuthorizeAttribute>());
        Assert.Contains(attributes,
            attribute => attribute.Policy == ProEntitlement.Policy);
    }

    private static (ProEntitlementHandler Handler, ApplicationUser User) Handler(params string[] roles)
    {
        var user = new ApplicationUser { Id = "user-1" };
        var users = new Mock<UserManager<ApplicationUser>>(
            Mock.Of<IUserStore<ApplicationUser>>(), null!, null!, null!, null!, null!, null!, null!, null!);
        users.Setup(x => x.FindByIdAsync(user.Id)).ReturnsAsync(user);
        users.Setup(x => x.GetRolesAsync(user)).ReturnsAsync(roles);
        return (new ProEntitlementHandler(users.Object), user);
    }

    private static AuthorizationHandlerContext Context(string userId, bool includeStalePremiumClaim)
    {
        var claims = new List<Claim> { new(ClaimTypes.NameIdentifier, userId) };
        if (includeStalePremiumClaim) claims.Add(new(ClaimTypes.Role, "Premium"));
        return new AuthorizationHandlerContext(
            new[] { new ProEntitlementRequirement() },
            new ClaimsPrincipal(new ClaimsIdentity(claims, "local")),
            null);
    }
}
