using System.Security.Claims;
using System.Text;
using JobTrackerApi.Controllers;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using JobTrackerApi.Tests.TestSupport;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class ClientErrorsControllerTests
{
    [Fact]
    public void Report_logs_sanitized_payload_instead_of_raw_stacks()
    {
        var logger = new ListLogger<ClientErrorsController>();
        var controller = new ClientErrorsController(logger);
        var stack = "TypeError: bad\n    at render(App.tsx:10)\nextra-secret-line";
        var componentStack = "at Widget\n at Dashboard";

        var result = controller.Report(new ClientErrorsController.ClientErrorReport(
            ErrorId: " err-1 ",
            Message: " boom ",
            Stack: stack,
            ComponentStack: componentStack,
            Url: " https://jobtracker.test/jobs ",
            UserAgent: " Browser\nAgent ",
            At: " 2026-04-10T18:00:00Z "));

        Assert.IsType<NoContentResult>(result);
        var entry = Assert.Single(logger.Entries);
        Assert.Contains("stackHash=", entry.Message);
        Assert.Contains("componentHash=", entry.Message);
        Assert.Contains("TypeError: bad | at render(App.tsx:10)", entry.Message);
        Assert.DoesNotContain(stack, entry.Message);
        Assert.DoesNotContain(componentStack, entry.Message);
        Assert.DoesNotContain("extra-secret-line", entry.Message);
        Assert.DoesNotContain("Browser\nAgent", entry.Message);
    }

    [Fact]
    public async Task Upload_avatar_rejects_file_when_extension_or_detected_bytes_are_not_supported()
    {
        var user = new ApplicationUser { Id = "user-1", Email = "person@example.com", UserName = "person@example.com" };
        var userManager = TestHostFactory.CreateUserManager();
        userManager.Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user);

        var controller = new AuthController(BuildConfig(), userManager.Object, Mock.Of<ITokenService>(), Mock.Of<IAppEmailSender>(), Mock.Of<IGoogleTokenValidator>(), Mock.Of<IMicrosoftTokenValidator>(), Mock.Of<ILogger<AuthController>>(), Mock.Of<ITwoFactorPendingTokenService>(), TestHostFactory.CreateInMemoryDb())
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "user-1") }, "local")) }
            }
        };

        await using var stream = new MemoryStream(Encoding.UTF8.GetBytes("not really a png"));
        IFormFile file = new FormFile(stream, 0, stream.Length, "file", "avatar.png")
        {
            Headers = new HeaderDictionary(),
            ContentType = "image/png"
        };

        var result = await controller.UploadAvatar(file);

        var badRequest = Assert.IsType<BadRequestObjectResult>(result);
        Assert.Equal("Only PNG, JPEG, or WebP images are supported.", badRequest.Value);
        userManager.Verify(x => x.UpdateAsync(It.IsAny<ApplicationUser>()), Times.Never);
    }

    private static IConfiguration BuildConfig()
    {
        return new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>())
            .Build();
    }

    private sealed class ListLogger<T> : ILogger<T>
    {
        public List<LogEntry> Entries { get; } = new();

        public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
        public bool IsEnabled(LogLevel logLevel) => true;

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            Entries.Add(new LogEntry(logLevel, formatter(state, exception)));
        }
    }

    private sealed record LogEntry(LogLevel Level, string Message);

    private sealed class NullScope : IDisposable
    {
        public static NullScope Instance { get; } = new();
        public void Dispose() { }
    }
}
