using System.Security.Claims;
using JobTrackerApi.Controllers;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class ImapControllerTests
{
    [Fact]
    public async Task Status_returns_connection_fields_for_connected_account()
    {
        var imap = new Mock<IImapService>();
        imap.Setup(service => service.GetConnectionAsync("user-1", It.IsAny<CancellationToken>()))
            .ReturnsAsync(new ImapConnection
            {
                OwnerUserId = "user-1",
                Host = "imap.example.test",
                Port = 993,
                UseSsl = true,
                Username = "user@example.test",
                ConnectedAt = DateTimeOffset.UtcNow.AddDays(-1),
                LastSyncStatus = "ok"
            });

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

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var payload = Assert.IsType<ImapController.ImapConnectionStatusDto>(ok.Value);
        Assert.True(payload.Connected);
        Assert.Equal("imap.example.test", payload.Host);
        Assert.Equal(993, payload.Port);
        Assert.Equal("user@example.test", payload.Username);
        Assert.Equal("ok", payload.LastSyncStatus);
    }

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

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

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var payload = Assert.IsType<ImapController.ImapConnectionStatusDto>(ok.Value);
        Assert.False(payload.Connected);
    }

    [Theory]
    [InlineData("", 993, "user", "pass", "Host is required.")]
    [InlineData("imap.example.test", 0, "user", "pass", "Valid port is required.")]
    [InlineData("imap.example.test", 993, "", "pass", "Username is required.")]
    [InlineData("imap.example.test", 993, "user", "", "Password is required.")]
    public async Task Connect_rejects_missing_fields(string host, int port, string username, string password, string expectedError)
    {
        var imap = new Mock<IImapService>(MockBehavior.Strict);
        var controller = CreateController(imap.Object, "user-1");

        var result = await controller.Connect(new ImapController.ImapConnectRequest(host, port, true, username, password), CancellationToken.None);

        var badRequest = Assert.IsType<BadRequestObjectResult>(result);
        Assert.Equal(expectedError, badRequest.Value);
    }

    [Fact]
    public async Task Connect_returns_bad_request_when_service_rejects_credentials()
    {
        var imap = new Mock<IImapService>();
        imap.Setup(service => service.ConnectAsync("user-1", "imap.example.test", 993, true, "user", "wrong", It.IsAny<CancellationToken>()))
            .ThrowsAsync(new InvalidOperationException("IMAP authentication failed: bad credentials"));

        var controller = CreateController(imap.Object, "user-1");
        var result = await controller.Connect(new ImapController.ImapConnectRequest("imap.example.test", 993, true, "user", "wrong"), CancellationToken.None);

        var badRequest = Assert.IsType<BadRequestObjectResult>(result);
        Assert.Contains("authentication failed", (string)badRequest.Value!);
    }

    [Fact]
    public async Task Connect_succeeds_and_returns_username()
    {
        var imap = new Mock<IImapService>();
        imap.Setup(service => service.ConnectAsync("user-1", "imap.example.test", 993, true, "user@example.test", "correct", It.IsAny<CancellationToken>()))
            .ReturnsAsync(new ImapConnectResult("user@example.test"));

        var controller = CreateController(imap.Object, "user-1");
        var result = await controller.Connect(new ImapController.ImapConnectRequest("imap.example.test", 993, true, "user@example.test", "correct"), CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result);
        var username = ok.Value!.GetType().GetProperty("username")!.GetValue(ok.Value) as string;
        Assert.Equal("user@example.test", username);
    }

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

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

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

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