using Xunit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using System.Security.Claims;
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Text.Json;
using LittleShop.Controllers;
using LittleShop.Services;
using LittleShop.DTOs;

namespace LittleShop.Tests.Unit;

public class PushNotificationControllerTests
{
    private readonly Mock<IPushNotificationService> _pushServiceMock;
    private readonly PushNotificationController _controller;
    private readonly Guid _testUserId = Guid.NewGuid();

    public PushNotificationControllerTests()
    {
        _pushServiceMock = new Mock<IPushNotificationService>();
        _controller = new PushNotificationController(_pushServiceMock.Object);
        
        // Setup controller context for authenticated user
        SetupControllerContext();
    }

    private void SetupControllerContext()
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, _testUserId.ToString()),
            new Claim(ClaimTypes.Name, "testuser"),
            new Claim(ClaimTypes.Role, "Admin")
        };
        var identity = new ClaimsIdentity(claims, "TestAuth");
        var principal = new ClaimsPrincipal(identity);

        // Setup service provider with logger
        var services = new ServiceCollection();
        services.AddLogging();
        var serviceProvider = services.BuildServiceProvider();

        var httpContext = new DefaultHttpContext
        {
            User = principal,
            Connection = { RemoteIpAddress = System.Net.IPAddress.Parse("192.168.1.1") },
            RequestServices = serviceProvider
        };

        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = httpContext
        };

        // Setup User-Agent header
        _controller.HttpContext.Request.Headers.Add("User-Agent", "TestBrowser/1.0");
    }

    private string GetPropertyFromResult(object resultValue, string propertyName)
    {
        var jsonString = JsonSerializer.Serialize(resultValue);
        var response = JsonSerializer.Deserialize<JsonElement>(jsonString);
        
        // Handle case where the result is a simple string value
        if (response.ValueKind == JsonValueKind.String)
        {
            return response.GetString()!;
        }
        
        return response.GetProperty(propertyName).GetString()!;
    }

    [Fact]
    public void GetVapidPublicKey_ReturnsPublicKey()
    {
        // Arrange
        var expectedKey = "test-public-key";
        _pushServiceMock.Setup(s => s.GetVapidPublicKey()).Returns(expectedKey);

        // Act
        var result = _controller.GetVapidPublicKey();

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var jsonString = JsonSerializer.Serialize(okResult.Value);
        var response = JsonSerializer.Deserialize<JsonElement>(jsonString);
        Assert.Equal(expectedKey, response.GetProperty("publicKey").GetString());
    }

    [Fact]
    public void GetVapidPublicKey_ReturnsInternalServerError_OnException()
    {
        // Arrange
        _pushServiceMock.Setup(s => s.GetVapidPublicKey()).Throws(new Exception("Test exception"));

        // Act
        var result = _controller.GetVapidPublicKey();

        // Assert
        var statusResult = Assert.IsType<ObjectResult>(result);
        Assert.Equal(500, statusResult.StatusCode);
    }

    [Fact]
    public async Task Subscribe_ReturnsOk_WhenSubscriptionSuccessful()
    {
        // Arrange
        var subscriptionDto = new PushSubscriptionDto
        {
            Endpoint = "https://fcm.googleapis.com/fcm/send/test",
            P256DH = "test-p256dh",
            Auth = "test-auth"
        };

        _pushServiceMock
            .Setup(s => s.SubscribeUserAsync(_testUserId, subscriptionDto, "TestBrowser/1.0", "192.168.1.1"))
            .ReturnsAsync(true);

        // Act
        var result = await _controller.Subscribe(subscriptionDto);

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var message = GetPropertyFromResult(okResult.Value!, "message");
        Assert.Equal("Successfully subscribed to push notifications", message);
    }

    [Fact]
    public async Task Subscribe_ReturnsInternalServerError_WhenSubscriptionFails()
    {
        // Arrange
        var subscriptionDto = new PushSubscriptionDto
        {
            Endpoint = "https://fcm.googleapis.com/fcm/send/test",
            P256DH = "test-p256dh",
            Auth = "test-auth"
        };

        _pushServiceMock
            .Setup(s => s.SubscribeUserAsync(It.IsAny<Guid>(), It.IsAny<PushSubscriptionDto>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(false);

        // Act
        var result = await _controller.Subscribe(subscriptionDto);

        // Assert
        var statusResult = Assert.IsType<ObjectResult>(result);
        Assert.Equal(500, statusResult.StatusCode);
    }

    [Fact]
    public async Task Subscribe_ReturnsUnauthorized_WhenUserIdInvalid()
    {
        // Arrange
        // Setup controller with invalid user ID
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, "invalid-guid"),
            new Claim(ClaimTypes.Role, "Admin")
        };
        var identity = new ClaimsIdentity(claims, "TestAuth");
        var principal = new ClaimsPrincipal(identity);

        _controller.ControllerContext.HttpContext.User = principal;

        var subscriptionDto = new PushSubscriptionDto
        {
            Endpoint = "https://fcm.googleapis.com/fcm/send/test",
            P256DH = "test-p256dh",
            Auth = "test-auth"
        };

        // Act
        var result = await _controller.Subscribe(subscriptionDto);

        // Assert
        var unauthorizedResult = Assert.IsType<UnauthorizedObjectResult>(result);
        var error = GetPropertyFromResult(unauthorizedResult.Value!, "error");
        Assert.Equal("Invalid user ID", error);
    }

    [Fact]
    public async Task SubscribeCustomer_ReturnsOk_WhenSuccessful()
    {
        // Arrange
        var customerId = Guid.NewGuid();
        var subscriptionDto = new PushSubscriptionDto
        {
            Endpoint = "https://fcm.googleapis.com/fcm/send/customer",
            P256DH = "customer-p256dh",
            Auth = "customer-auth"
        };

        _pushServiceMock
            .Setup(s => s.SubscribeCustomerAsync(customerId, subscriptionDto, It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(true);

        // Act
        var result = await _controller.SubscribeCustomer(subscriptionDto, customerId);

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var message = GetPropertyFromResult(okResult.Value!, "message");
        Assert.Equal("Successfully subscribed to push notifications", message);
    }

    [Fact]
    public async Task SubscribeCustomer_ReturnsBadRequest_WhenCustomerIdEmpty()
    {
        // Arrange
        var subscriptionDto = new PushSubscriptionDto
        {
            Endpoint = "https://fcm.googleapis.com/fcm/send/customer",
            P256DH = "customer-p256dh",
            Auth = "customer-auth"
        };

        // Act
        var result = await _controller.SubscribeCustomer(subscriptionDto, Guid.Empty);

        // Assert
        var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
        var error = GetPropertyFromResult(badRequestResult.Value!, "error");
        Assert.Equal("Invalid customer ID", error);
    }

    [Fact]
    public async Task Unsubscribe_ReturnsOk_WhenSuccessful()
    {
        // Arrange
        var unsubscribeDto = new UnsubscribeDto { Endpoint = "https://fcm.googleapis.com/fcm/send/test" };
        _pushServiceMock.Setup(s => s.UnsubscribeAsync(unsubscribeDto.Endpoint)).ReturnsAsync(true);

        // Act
        var result = await _controller.Unsubscribe(unsubscribeDto);

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var message = GetPropertyFromResult(okResult.Value!, "message");
        Assert.Equal("Successfully unsubscribed from push notifications", message);
    }

    [Fact]
    public async Task Unsubscribe_ReturnsNotFound_WhenSubscriptionNotFound()
    {
        // Arrange
        var unsubscribeDto = new UnsubscribeDto { Endpoint = "https://fcm.googleapis.com/fcm/send/nonexistent" };
        _pushServiceMock.Setup(s => s.UnsubscribeAsync(unsubscribeDto.Endpoint)).ReturnsAsync(false);

        // Act
        var result = await _controller.Unsubscribe(unsubscribeDto);

        // Assert
        var notFoundResult = Assert.IsType<NotFoundObjectResult>(result);
        var error = GetPropertyFromResult(notFoundResult.Value!, "error");
        Assert.Equal("Subscription not found", error);
    }

    [Fact]
    public async Task SendTestNotification_ReturnsOk_WhenSuccessful()
    {
        // Arrange
        var testDto = new TestPushNotificationDto
        {
            Title = "Test Title",
            Body = "Test Body"
        };

        _pushServiceMock
            .Setup(s => s.SendTestNotificationAsync(_testUserId.ToString(), testDto.Title, testDto.Body))
            .ReturnsAsync(true);

        // Act
        var result = await _controller.SendTestNotification(testDto);

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var message = GetPropertyFromResult(okResult.Value!, "message");
        Assert.Equal("Test notification sent successfully", message);
    }

    [Fact]
    public async Task SendTestNotification_ReturnsInternalServerError_WhenFails()
    {
        // Arrange
        var testDto = new TestPushNotificationDto
        {
            Title = "Test Title",
            Body = "Test Body"
        };

        _pushServiceMock
            .Setup(s => s.SendTestNotificationAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(false);

        // Act
        var result = await _controller.SendTestNotification(testDto);

        // Assert
        var statusResult = Assert.IsType<ObjectResult>(result);
        Assert.Equal(500, statusResult.StatusCode);
        var error = GetPropertyFromResult(statusResult.Value!, "error");
        Assert.Contains("Failed to send test notification", error);
    }

    [Fact]
    public async Task SendBroadcastNotification_ReturnsOk_WhenSuccessful()
    {
        // Arrange
        var notificationDto = new PushNotificationDto
        {
            Title = "Broadcast Title",
            Body = "Broadcast Body"
        };

        _pushServiceMock
            .Setup(s => s.SendNotificationToAllUsersAsync(notificationDto))
            .ReturnsAsync(true);

        // Act
        var result = await _controller.SendBroadcastNotification(notificationDto);

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var message = GetPropertyFromResult(okResult.Value!, "message");
        Assert.Equal("Broadcast notification sent successfully", message);
    }

    [Fact]
    public async Task GetActiveSubscriptions_ReturnsOk_WithSubscriptions()
    {
        // Arrange
        var subscriptions = new List<LittleShop.Models.PushSubscription>
        {
            new LittleShop.Models.PushSubscription
            {
                Id = 1,
                UserId = _testUserId,
                Endpoint = "https://fcm.googleapis.com/fcm/send/test-long-endpoint-that-should-be-truncated",
                SubscribedAt = DateTime.UtcNow,
                LastUsedAt = DateTime.UtcNow,
                UserAgent = "TestBrowser/1.0",
                User = new LittleShop.Models.User { Username = "testuser" }
            }
        };

        _pushServiceMock.Setup(s => s.GetActiveSubscriptionsAsync()).ReturnsAsync(subscriptions);

        // Act
        var result = await _controller.GetActiveSubscriptions();

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var jsonString = JsonSerializer.Serialize(okResult.Value);
        var subscriptionArray = JsonSerializer.Deserialize<JsonElement[]>(jsonString);
        Assert.NotNull(subscriptionArray);
        Assert.Single(subscriptionArray);
    }

    [Fact]
    public async Task CleanupExpiredSubscriptions_ReturnsOk_WithCleanupCount()
    {
        // Arrange
        _pushServiceMock.Setup(s => s.CleanupExpiredSubscriptionsAsync()).ReturnsAsync(5);

        // Act
        var result = await _controller.CleanupExpiredSubscriptions();

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var message = GetPropertyFromResult(okResult.Value!, "message");
        Assert.Equal("Cleaned up 5 expired subscriptions", message);
    }
}