using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using UniVerse.Application.DTOs.Notifications;
using UniVerse.Application.Interfaces;
using UniVerse.Domain.Entities;
using UniVerse.Domain.Enums;
using UniVerse.Domain.Exceptions;
using UniVerse.Infrastructure.Data;
using UniVerse.Infrastructure.Services;
using Xunit;

namespace UniVerse.Api.Tests.Auth;

public class AuthServiceTests
{
    [Fact]
    public async Task RefreshTokenAsync_InactiveUser_RevokesTokenAndThrowsForbidden()
    {
        await using var db = CreateDbContext();
        db.Users.Add(new User
        {
            Id = 1,
            Email = "blocked@test.local",
            IsActive = false,
            Roles = [new UserRoleAssignment { UserId = 1, Role = UserRole.Student }]
        });
        db.RefreshTokens.Add(new RefreshToken
        {
            Id = 1,
            UserId = 1,
            Token = "refresh-token",
            ExpiresAt = DateTime.UtcNow.AddDays(1),
            CreatedAt = DateTime.UtcNow
        });
        await db.SaveChangesAsync();
        var service = CreateService(db);

        await Assert.ThrowsAsync<ForbiddenException>(() => service.RefreshTokenAsync("refresh-token"));

        var token = await db.RefreshTokens.SingleAsync(t => t.Token == "refresh-token");
        Assert.NotNull(token.RevokedAt);
    }

    [Fact]
    public async Task GetCurrentUserAsync_InactiveUser_ThrowsForbidden()
    {
        await using var db = CreateDbContext();
        db.Users.Add(new User
        {
            Id = 1,
            Email = "blocked@test.local",
            IsActive = false,
            Roles = [new UserRoleAssignment { UserId = 1, Role = UserRole.Student }]
        });
        await db.SaveChangesAsync();
        var service = CreateService(db);

        await Assert.ThrowsAsync<ForbiddenException>(() => service.GetCurrentUserAsync(1));
    }

    [Fact]
    public async Task LoginWithMicrosoftAsync_LinksScheduleTeacherBySubId()
    {
        await using var db = CreateDbContext();
        db.Users.Add(new User
        {
            Id = 10,
            Email = "modeus-person-1@modeus.local",
            DisplayName = "Иванов Иван Иванович",
            MicrosoftId = "sso-sub-1",
            IsActive = true,
            Roles = [new UserRoleAssignment { UserId = 10, Role = UserRole.Teacher }],
            TeacherProfile = new TeacherProfile { UserId = 10, ModeusId = "person-1" }
        });
        await db.SaveChangesAsync();
        var microsoftAuth = Substitute.For<IMicrosoftAuthClient>();
        microsoftAuth.ExchangeAuthorizationCodeAsync("code", "http://localhost/callback", Arg.Any<CancellationToken>())
            .Returns(new MicrosoftTokenResult(BuildIdToken("sso-sub-1", "teacher@sfedu.ru", "Иванов Иван Иванович")));
        var service = CreateService(db, microsoftAuth);

        var result = await service.LoginWithMicrosoftAsync("code", "http://localhost/callback");

        Assert.Equal(10, result.Response.User.Id);
        Assert.Equal("teacher@sfedu.ru", result.Response.User.Email);
        Assert.Contains(UserRole.Teacher, result.Response.User.Roles);
        Assert.Single(await db.Users.ToListAsync());
        var user = await db.Users.Include(u => u.TeacherProfile).SingleAsync();
        Assert.Equal("sso-sub-1", user.MicrosoftId);
        Assert.Equal("person-1", user.TeacherProfile?.ModeusId);
    }

    private static AppDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase($"AuthServiceTests_{Guid.NewGuid()}")
            .Options;
        return new AppDbContext(options);
    }

    private static AuthService CreateService(AppDbContext db, IMicrosoftAuthClient? microsoftAuth = null)
    {
        var config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Jwt:Secret"] = "test-secret-test-secret-test-secret-test-secret",
                ["Jwt:Issuer"] = "UniVerse.Tests",
                ["Jwt:Audience"] = "UniVerse.Tests",
                ["Jwt:AccessTokenExpirationMinutes"] = "15",
                ["Jwt:RefreshTokenExpirationDays"] = "30"
            })
            .Build();

        var gamification = Substitute.For<IGamificationService>();
        gamification.CalculateLevelAsync(Arg.Any<int>()).Returns(1);

        var notifications = Substitute.For<INotificationService>();
        notifications.SendAsync(Arg.Any<NotificationMessage>(), Arg.Any<CancellationToken>())
            .Returns(Task.CompletedTask);

        microsoftAuth ??= Substitute.For<IMicrosoftAuthClient>();
        return new AuthService(db, config, microsoftAuth, gamification, notifications, NullLogger<AuthService>.Instance);
    }

    private static string BuildIdToken(string sub, string email, string name)
    {
        var token = new JwtSecurityToken(claims:
        [
            new Claim(JwtRegisteredClaimNames.Sub, sub),
            new Claim("preferred_username", email),
            new Claim("name", name)
        ]);
        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
