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

namespace JobTrackerApi.Tests;

public sealed class MicrosoftGraphControllerTests
{
    [Fact]
    public async Task Status_returns_sync_state_fields_for_connected_account()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>();
        graph.Setup(service => service.GetConnectionAsync("user-1", It.IsAny<CancellationToken>()))
            .ReturnsAsync(new MicrosoftGraphConnection
            {
                OwnerUserId = "user-1",
                MailAddress = "user@outlook.test",
                ConnectedAt = DateTimeOffset.UtcNow.AddDays(-2),
                LastSyncedAt = DateTimeOffset.UtcNow.AddMinutes(-10),
                LastSyncAttemptedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
                LastSyncSucceededAt = DateTimeOffset.UtcNow.AddMinutes(-10),
                LastSyncMode = "list-messages",
                LastSyncSource = "custom-query",
                LastSyncStatus = "error",
                LastSyncError = "Token refresh failed"
            });

        var controller = CreateController(graph.Object, "user-1");
        var result = await controller.Status(CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var payload = Assert.IsType<MicrosoftGraphController.MicrosoftGraphConnectionStatusDto>(ok.Value);
        Assert.True(payload.Connected);
        Assert.Equal("user@outlook.test", payload.MailAddress);
        Assert.Equal("list-messages", payload.LastSyncMode);
        Assert.Equal("custom-query", payload.LastSyncSource);
        Assert.Equal("error", payload.LastSyncStatus);
        Assert.Equal("Token refresh failed", payload.LastSyncError);
    }

    [Fact]
    public async Task Status_reports_not_connected_when_no_connection_exists()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>();
        graph.Setup(service => service.GetConnectionAsync("user-1", It.IsAny<CancellationToken>()))
            .ReturnsAsync((MicrosoftGraphConnection?)null);

        var controller = CreateController(graph.Object, "user-1");
        var result = await controller.Status(CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var payload = Assert.IsType<MicrosoftGraphController.MicrosoftGraphConnectionStatusDto>(ok.Value);
        Assert.False(payload.Connected);
        Assert.Null(payload.MailAddress);
    }

    [Fact]
    public void ConnectUrl_returns_authorization_url_from_service()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>();
        graph.Setup(service => service.BuildAuthorizationUrl("user-1", It.IsAny<string>()))
            .Returns("https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=test");

        var controller = CreateController(graph.Object, "user-1");
        var result = controller.ConnectUrl();

        var ok = Assert.IsType<OkObjectResult>(result);
        var url = ok.Value!.GetType().GetProperty("url")!.GetValue(ok.Value) as string;
        Assert.Equal("https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=test", url);
    }

    [Fact]
    public async Task Callback_returns_error_html_when_state_is_invalid()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>();
        graph.Setup(service => service.ConsumeState("bad-state")).Returns((string?)null);

        var controller = CreateController(graph.Object, "user-1");
        var result = await controller.Callback("auth-code", "bad-state", null, CancellationToken.None);

        var content = Assert.IsType<ContentResult>(result);
        Assert.Contains("no longer valid", content.Content);
        graph.Verify(service => service.ExchangeCodeAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
    }

    [Fact]
    public async Task Callback_returns_error_html_when_provider_returns_error()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>(MockBehavior.Strict);
        var controller = CreateController(graph.Object, "user-1");

        var result = await controller.Callback(null, null, "access_denied", CancellationToken.None);

        var content = Assert.IsType<ContentResult>(result);
        Assert.Contains("access_denied", content.Content);
    }

    [Fact]
    public async Task Callback_exchanges_code_and_reports_connected_mail_address()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>();
        graph.Setup(service => service.ConsumeState("good-state")).Returns("user-1");
        graph.Setup(service => service.ExchangeCodeAsync("user-1", "auth-code", It.IsAny<string>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new MicrosoftGraphOAuthExchangeResult("user@outlook.test"));

        var controller = CreateController(graph.Object, "user-1");
        var result = await controller.Callback("auth-code", "good-state", null, CancellationToken.None);

        var content = Assert.IsType<ContentResult>(result);
        Assert.Contains("user@outlook.test", content.Content);
    }

    [Fact]
    public async Task Disconnect_calls_service_for_authenticated_user()
    {
        var graph = new Mock<IMicrosoftGraphOAuthService>();
        graph.Setup(service => service.DisconnectAsync("user-1", It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);

        var controller = CreateController(graph.Object, "user-1");
        var result = await controller.Disconnect(CancellationToken.None);

        Assert.IsType<NoContentResult>(result);
        graph.Verify(service => service.DisconnectAsync("user-1", It.IsAny<CancellationToken>()), Times.Once);
    }

    private static MicrosoftGraphController CreateController(IMicrosoftGraphOAuthService graph, string userId)
    {
        return new MicrosoftGraphController(graph, BuildConfig())
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = new DefaultHttpContext
                {
                    User = new ClaimsPrincipal(new ClaimsIdentity(new[]
                    {
                        new Claim(ClaimTypes.NameIdentifier, userId)
                    }, "test"))
                }
            }
        };
    }

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