using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Serilog;
using WebPush;
using System.Text.Json;
using LittleShop.Data;
using LittleShop.DTOs;
using LittleShop.Models;

namespace LittleShop.Services;

public class PushNotificationService : IPushNotificationService
{
    private readonly LittleShopContext _context;
    private readonly IConfiguration _configuration;
    private readonly WebPushClient _webPushClient;
    private readonly VapidDetails _vapidDetails;

    public PushNotificationService(LittleShopContext context, IConfiguration configuration)
    {
        _context = context;
        _configuration = configuration;
        
        // Initialize VAPID details
        _vapidDetails = new VapidDetails(
            subject: _configuration["WebPush:Subject"] ?? "mailto:admin@littleshop.local",
            publicKey: _configuration["WebPush:VapidPublicKey"] ?? throw new InvalidOperationException("WebPush:VapidPublicKey not configured"),
            privateKey: _configuration["WebPush:VapidPrivateKey"] ?? throw new InvalidOperationException("WebPush:VapidPrivateKey not configured")
        );
        
        _webPushClient = new WebPushClient();
    }

    public string GetVapidPublicKey()
    {
        return _vapidDetails.PublicKey;
    }

    public async Task<bool> SubscribeUserAsync(Guid userId, PushSubscriptionDto subscriptionDto, string? userAgent = null, string? ipAddress = null)
    {
        try
        {
            // Check if the user actually exists in the database
            var userExists = await _context.Users.AnyAsync(u => u.Id == userId);
            if (!userExists)
            {
                Log.Warning("Attempted to subscribe non-existent user {UserId} to push notifications", userId);
                return false;
            }
            
            // Check if subscription already exists
            var existingSubscription = await _context.PushSubscriptions
                .FirstOrDefaultAsync(ps => ps.Endpoint == subscriptionDto.Endpoint && ps.UserId == userId);

            if (existingSubscription != null)
            {
                // Update existing subscription
                existingSubscription.P256DH = subscriptionDto.P256DH;
                existingSubscription.Auth = subscriptionDto.Auth;
                existingSubscription.LastUsedAt = DateTime.UtcNow;
                existingSubscription.IsActive = true;
                existingSubscription.UserAgent = userAgent;
                existingSubscription.IpAddress = ipAddress;
                Log.Information("Updated existing push subscription for user {UserId}", userId);
            }
            else
            {
                // Create new subscription
                var subscription = new Models.PushSubscription
                {
                    UserId = userId,
                    Endpoint = subscriptionDto.Endpoint,
                    P256DH = subscriptionDto.P256DH,
                    Auth = subscriptionDto.Auth,
                    SubscribedAt = DateTime.UtcNow,
                    LastUsedAt = DateTime.UtcNow,
                    IsActive = true,
                    UserAgent = userAgent,
                    IpAddress = ipAddress
                };

                _context.PushSubscriptions.Add(subscription);
                Log.Information("Created new push subscription for user {UserId}", userId);
            }

            await _context.SaveChangesAsync();
            Log.Information("Push subscription saved successfully for user {UserId}", userId);
            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to subscribe user {UserId} to push notifications", userId);
            return false;
        }
    }

    public async Task<bool> SubscribeCustomerAsync(Guid customerId, PushSubscriptionDto subscriptionDto, string? userAgent = null, string? ipAddress = null)
    {
        try
        {
            // Check if subscription already exists
            var existingSubscription = await _context.PushSubscriptions
                .FirstOrDefaultAsync(ps => ps.Endpoint == subscriptionDto.Endpoint && ps.CustomerId == customerId);

            if (existingSubscription != null)
            {
                // Update existing subscription
                existingSubscription.P256DH = subscriptionDto.P256DH;
                existingSubscription.Auth = subscriptionDto.Auth;
                existingSubscription.LastUsedAt = DateTime.UtcNow;
                existingSubscription.IsActive = true;
                existingSubscription.UserAgent = userAgent;
                existingSubscription.IpAddress = ipAddress;
            }
            else
            {
                // Create new subscription
                var subscription = new Models.PushSubscription
                {
                    CustomerId = customerId,
                    Endpoint = subscriptionDto.Endpoint,
                    P256DH = subscriptionDto.P256DH,
                    Auth = subscriptionDto.Auth,
                    SubscribedAt = DateTime.UtcNow,
                    LastUsedAt = DateTime.UtcNow,
                    IsActive = true,
                    UserAgent = userAgent,
                    IpAddress = ipAddress
                };

                _context.PushSubscriptions.Add(subscription);
            }

            await _context.SaveChangesAsync();
            Log.Information("Push subscription created/updated for customer {CustomerId}", customerId);
            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to subscribe customer {CustomerId} to push notifications", customerId);
            return false;
        }
    }

    public async Task<bool> UnsubscribeAsync(string endpoint)
    {
        try
        {
            var subscription = await _context.PushSubscriptions
                .FirstOrDefaultAsync(ps => ps.Endpoint == endpoint);

            if (subscription != null)
            {
                subscription.IsActive = false;
                await _context.SaveChangesAsync();
                Log.Information("Push subscription unsubscribed for endpoint {Endpoint}", endpoint);
                return true;
            }

            return false;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to unsubscribe endpoint {Endpoint}", endpoint);
            return false;
        }
    }

    public async Task<bool> SendNotificationToUserAsync(Guid userId, PushNotificationDto notification)
    {
        var subscriptions = await _context.PushSubscriptions
            .Where(ps => ps.UserId == userId && ps.IsActive)
            .ToListAsync();

        return await SendNotificationToSubscriptions(subscriptions, notification);
    }

    public async Task<bool> SendNotificationToCustomerAsync(Guid customerId, PushNotificationDto notification)
    {
        var subscriptions = await _context.PushSubscriptions
            .Where(ps => ps.CustomerId == customerId && ps.IsActive)
            .ToListAsync();

        return await SendNotificationToSubscriptions(subscriptions, notification);
    }

    public async Task<bool> SendNotificationToAllUsersAsync(PushNotificationDto notification)
    {
        var subscriptions = await _context.PushSubscriptions
            .Where(ps => ps.UserId != null && ps.IsActive)
            .ToListAsync();

        return await SendNotificationToSubscriptions(subscriptions, notification);
    }

    public async Task<bool> SendNotificationToAllCustomersAsync(PushNotificationDto notification)
    {
        var subscriptions = await _context.PushSubscriptions
            .Where(ps => ps.CustomerId != null && ps.IsActive)
            .ToListAsync();

        return await SendNotificationToSubscriptions(subscriptions, notification);
    }

    public async Task<bool> SendOrderNotificationAsync(Guid orderId, string title, string body)
    {
        try
        {
            var order = await _context.Orders
                .Include(o => o.Customer)
                .FirstOrDefaultAsync(o => o.Id == orderId);

            if (order == null)
                return false;

            var notification = new PushNotificationDto
            {
                Title = title,
                Body = body,
                Icon = "/icons/icon-192x192.png",
                Badge = "/icons/icon-72x72.png",
                Url = $"/Admin/Orders/Details/{orderId}",
                Data = new { orderId = orderId, type = "order" }
            };

            // Send to all admin users
            await SendNotificationToAllUsersAsync(notification);

            // Send to customer if they have push subscription
            if (order.CustomerId.HasValue)
            {
                var customerNotification = new PushNotificationDto
                {
                    Title = title,
                    Body = body,
                    Icon = "/icons/icon-192x192.png",
                    Badge = "/icons/icon-72x72.png",
                    Data = new { orderId = orderId, type = "order" }
                };
                await SendNotificationToCustomerAsync(order.CustomerId.Value, customerNotification);
            }

            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to send order notification for order {OrderId}", orderId);
            return false;
        }
    }

    public async Task<bool> SendTestNotificationAsync(string? userId = null, string title = "Test Notification", string body = "This is a test notification from LittleShop")
    {
        var notification = new PushNotificationDto
        {
            Title = title,
            Body = body,
            Icon = "/icons/icon-192x192.png",
            Badge = "/icons/icon-72x72.png",
            Data = new { type = "test", timestamp = DateTime.UtcNow }
        };

        if (!string.IsNullOrEmpty(userId) && Guid.TryParse(userId, out Guid userIdGuid))
        {
            return await SendNotificationToUserAsync(userIdGuid, notification);
        }
        else
        {
            // Send to all admin users
            return await SendNotificationToAllUsersAsync(notification);
        }
    }

    private async Task<bool> SendNotificationToSubscriptions(List<Models.PushSubscription> subscriptions, PushNotificationDto notification)
    {
        if (!subscriptions.Any())
            return false;

        int successCount = 0;
        var failedSubscriptions = new List<Models.PushSubscription>();

        var payload = JsonSerializer.Serialize(new
        {
            title = notification.Title,
            body = notification.Body,
            icon = notification.Icon ?? "/icons/icon-192x192.png",
            badge = notification.Badge ?? "/icons/icon-72x72.png",
            url = notification.Url,
            data = notification.Data
        });

        foreach (var subscription in subscriptions)
        {
            try
            {
                var pushSubscription = new WebPush.PushSubscription(
                    endpoint: subscription.Endpoint,
                    p256dh: subscription.P256DH,
                    auth: subscription.Auth
                );

                await _webPushClient.SendNotificationAsync(pushSubscription, payload, _vapidDetails);
                
                // Update last used time
                subscription.LastUsedAt = DateTime.UtcNow;
                successCount++;

                Log.Information("Push notification sent successfully to endpoint {Endpoint}", subscription.Endpoint);
            }
            catch (WebPushException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Gone)
            {
                // Subscription is no longer valid, mark as inactive
                subscription.IsActive = false;
                failedSubscriptions.Add(subscription);
                Log.Warning("Push subscription expired for endpoint {Endpoint}", subscription.Endpoint);
            }
            catch (Exception ex)
            {
                failedSubscriptions.Add(subscription);
                Log.Error(ex, "Failed to send push notification to endpoint {Endpoint}", subscription.Endpoint);
            }
        }

        // Save changes to update last used times and inactive subscriptions
        await _context.SaveChangesAsync();

        Log.Information("Push notifications sent: {SuccessCount} successful, {FailedCount} failed", 
            successCount, failedSubscriptions.Count);

        return successCount > 0;
    }

    public async Task<int> CleanupExpiredSubscriptionsAsync()
    {
        try
        {
            var thirtyDaysAgo = DateTime.UtcNow.AddDays(-30);
            
            var expiredSubscriptions = await _context.PushSubscriptions
                .Where(ps => ps.LastUsedAt < thirtyDaysAgo || !ps.IsActive)
                .ToListAsync();

            _context.PushSubscriptions.RemoveRange(expiredSubscriptions);
            await _context.SaveChangesAsync();

            Log.Information("Cleaned up {Count} expired push subscriptions", expiredSubscriptions.Count);
            return expiredSubscriptions.Count;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to cleanup expired push subscriptions");
            return 0;
        }
    }

    public async Task<List<Models.PushSubscription>> GetActiveSubscriptionsAsync()
    {
        return await _context.PushSubscriptions
            .Where(ps => ps.IsActive)
            .Include(ps => ps.User)
            .Include(ps => ps.Customer)
            .OrderByDescending(ps => ps.SubscribedAt)
            .ToListAsync();
    }
}