using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Stripe;
using Stripe.Checkout;
using RealCV.Application.Interfaces;
using RealCV.Domain.Enums;
using RealCV.Infrastructure.Configuration;
using RealCV.Infrastructure.Data;

namespace RealCV.Infrastructure.Services;

public sealed class StripeService : IStripeService
{
    private readonly ApplicationDbContext _dbContext;
    private readonly StripeSettings _settings;
    private readonly ILogger<StripeService> _logger;

    public StripeService(
        ApplicationDbContext dbContext,
        IOptions<StripeSettings> settings,
        ILogger<StripeService> logger)
    {
        _dbContext = dbContext;
        _settings = settings.Value;
        _logger = logger;

        StripeConfiguration.ApiKey = _settings.SecretKey;
    }

    public async Task<string> CreateCheckoutSessionAsync(
        Guid userId,
        string email,
        UserPlan targetPlan,
        string successUrl,
        string cancelUrl)
    {
        _logger.LogInformation("Creating checkout session for user {UserId}, plan {Plan}", userId, targetPlan);

        var priceId = targetPlan switch
        {
            UserPlan.Professional => _settings.PriceIds.Professional,
            UserPlan.Enterprise => _settings.PriceIds.Enterprise,
            _ => throw new ArgumentException($"Invalid plan for checkout: {targetPlan}")
        };

        if (string.IsNullOrEmpty(priceId))
        {
            throw new InvalidOperationException($"Price ID not configured for plan: {targetPlan}");
        }

        var user = await _dbContext.Users.FindAsync(userId);
        if (user == null)
        {
            throw new InvalidOperationException($"User not found: {userId}");
        }

        var sessionOptions = new SessionCreateOptions
        {
            Mode = "subscription",
            CustomerEmail = string.IsNullOrEmpty(user.StripeCustomerId) ? email : null,
            Customer = !string.IsNullOrEmpty(user.StripeCustomerId) ? user.StripeCustomerId : null,
            LineItems = new List<SessionLineItemOptions>
            {
                new()
                {
                    Price = priceId,
                    Quantity = 1
                }
            },
            SuccessUrl = successUrl + "?session_id={CHECKOUT_SESSION_ID}",
            CancelUrl = cancelUrl,
            Metadata = new Dictionary<string, string>
            {
                { "user_id", userId.ToString() },
                { "target_plan", targetPlan.ToString() }
            },
            SubscriptionData = new SessionSubscriptionDataOptions
            {
                Metadata = new Dictionary<string, string>
                {
                    { "user_id", userId.ToString() },
                    { "plan", targetPlan.ToString() }
                }
            }
        };

        var sessionService = new SessionService();
        var session = await sessionService.CreateAsync(sessionOptions);

        _logger.LogInformation("Checkout session created: {SessionId}", session.Id);

        return session.Url;
    }

    public async Task<string> CreateCustomerPortalSessionAsync(string stripeCustomerId, string returnUrl)
    {
        _logger.LogInformation("Creating customer portal session for customer {CustomerId}", stripeCustomerId);

        var options = new Stripe.BillingPortal.SessionCreateOptions
        {
            Customer = stripeCustomerId,
            ReturnUrl = returnUrl
        };

        var service = new Stripe.BillingPortal.SessionService();
        var session = await service.CreateAsync(options);

        return session.Url;
    }

    public async Task HandleWebhookAsync(string json, string signature)
    {
        Event stripeEvent;

        try
        {
            stripeEvent = EventUtility.ConstructEvent(json, signature, _settings.WebhookSecret);
        }
        catch (StripeException ex)
        {
            _logger.LogError(ex, "Webhook signature verification failed");
            throw;
        }

        _logger.LogInformation("Processing webhook event: {EventType} ({EventId})", stripeEvent.Type, stripeEvent.Id);

        switch (stripeEvent.Type)
        {
            case EventTypes.CheckoutSessionCompleted:
                await HandleCheckoutSessionCompleted(stripeEvent);
                break;

            case EventTypes.CustomerSubscriptionUpdated:
                await HandleSubscriptionUpdated(stripeEvent);
                break;

            case EventTypes.CustomerSubscriptionDeleted:
                await HandleSubscriptionDeleted(stripeEvent);
                break;

            case EventTypes.InvoicePaymentFailed:
                await HandlePaymentFailed(stripeEvent);
                break;

            default:
                _logger.LogDebug("Unhandled webhook event type: {EventType}", stripeEvent.Type);
                break;
        }
    }

    private async Task HandleCheckoutSessionCompleted(Event stripeEvent)
    {
        var session = stripeEvent.Data.Object as Session;
        if (session == null)
        {
            _logger.LogWarning("Could not parse checkout session from event");
            return;
        }

        var userIdString = session.Metadata.GetValueOrDefault("user_id");
        var targetPlanString = session.Metadata.GetValueOrDefault("target_plan");

        if (string.IsNullOrEmpty(userIdString) || !Guid.TryParse(userIdString, out var userId))
        {
            _logger.LogWarning("Missing or invalid user_id in checkout session metadata");
            return;
        }

        if (string.IsNullOrEmpty(targetPlanString) || !Enum.TryParse<UserPlan>(targetPlanString, out var targetPlan))
        {
            _logger.LogWarning("Missing or invalid target_plan in checkout session metadata");
            return;
        }

        var user = await _dbContext.Users.FindAsync(userId);
        if (user == null)
        {
            _logger.LogWarning("User not found for checkout session: {UserId}", userId);
            return;
        }

        user.StripeCustomerId = session.CustomerId;
        user.StripeSubscriptionId = session.SubscriptionId;
        user.Plan = targetPlan;
        user.SubscriptionStatus = "active";
        user.ChecksUsedThisMonth = 0;

        // Fetch subscription to get period end (from the first item)
        if (!string.IsNullOrEmpty(session.SubscriptionId))
        {
            var stripeSubscriptionService = new Stripe.SubscriptionService();
            var stripeSubscription = await stripeSubscriptionService.GetAsync(session.SubscriptionId);
            var firstItem = stripeSubscription.Items?.Data?.FirstOrDefault();
            if (firstItem != null)
            {
                user.CurrentPeriodEnd = firstItem.CurrentPeriodEnd;
            }
        }

        await _dbContext.SaveChangesAsync();

        _logger.LogInformation(
            "User {UserId} upgraded to {Plan} via checkout session {SessionId}",
            userId, targetPlan, session.Id);
    }

    private async Task HandleSubscriptionUpdated(Event stripeEvent)
    {
        var stripeSubscription = stripeEvent.Data.Object as Stripe.Subscription;
        if (stripeSubscription == null)
        {
            _logger.LogWarning("Could not parse subscription from event");
            return;
        }

        var user = await _dbContext.Users
            .FirstOrDefaultAsync(u => u.StripeSubscriptionId == stripeSubscription.Id);

        if (user == null)
        {
            _logger.LogDebug("No user found for subscription: {SubscriptionId}", stripeSubscription.Id);
            return;
        }

        var previousStatus = user.SubscriptionStatus;
        var previousPeriodEnd = user.CurrentPeriodEnd;

        user.SubscriptionStatus = stripeSubscription.Status;

        // Get period end from first subscription item
        var firstItem = stripeSubscription.Items?.Data?.FirstOrDefault();
        var newPeriodEnd = firstItem?.CurrentPeriodEnd;
        user.CurrentPeriodEnd = newPeriodEnd;

        // Reset usage if billing period renewed
        if (previousPeriodEnd.HasValue &&
            newPeriodEnd.HasValue &&
            newPeriodEnd.Value > previousPeriodEnd.Value &&
            stripeSubscription.Status == "active")
        {
            user.ChecksUsedThisMonth = 0;
            _logger.LogInformation("Reset monthly usage for user {UserId} - new billing period", user.Id);
        }

        // Handle plan changes from Stripe portal
        var planString = stripeSubscription.Metadata.GetValueOrDefault("plan");
        if (!string.IsNullOrEmpty(planString) && Enum.TryParse<UserPlan>(planString, out var plan))
        {
            user.Plan = plan;
        }

        await _dbContext.SaveChangesAsync();

        _logger.LogInformation(
            "Subscription updated for user {UserId}: status {Status}, period end {PeriodEnd}",
            user.Id, stripeSubscription.Status, newPeriodEnd);
    }

    private async Task HandleSubscriptionDeleted(Event stripeEvent)
    {
        var stripeSubscription = stripeEvent.Data.Object as Stripe.Subscription;
        if (stripeSubscription == null)
        {
            _logger.LogWarning("Could not parse subscription from event");
            return;
        }

        var user = await _dbContext.Users
            .FirstOrDefaultAsync(u => u.StripeSubscriptionId == stripeSubscription.Id);

        if (user == null)
        {
            _logger.LogDebug("No user found for deleted subscription: {SubscriptionId}", stripeSubscription.Id);
            return;
        }

        user.Plan = UserPlan.Free;
        user.StripeSubscriptionId = null;
        user.SubscriptionStatus = null;
        user.CurrentPeriodEnd = null;
        user.ChecksUsedThisMonth = 0;

        await _dbContext.SaveChangesAsync();

        _logger.LogInformation(
            "User {UserId} downgraded to Free plan - subscription {SubscriptionId} deleted",
            user.Id, stripeSubscription.Id);
    }

    private async Task HandlePaymentFailed(Event stripeEvent)
    {
        var invoice = stripeEvent.Data.Object as Invoice;
        if (invoice == null)
        {
            _logger.LogWarning("Could not parse invoice from event");
            return;
        }

        var user = await _dbContext.Users
            .FirstOrDefaultAsync(u => u.StripeCustomerId == invoice.CustomerId);

        if (user == null)
        {
            _logger.LogDebug("No user found for customer: {CustomerId}", invoice.CustomerId);
            return;
        }

        user.SubscriptionStatus = "past_due";
        await _dbContext.SaveChangesAsync();

        _logger.LogWarning(
            "Payment failed for user {UserId}, invoice {InvoiceId}. Subscription marked as past_due.",
            user.Id, invoice.Id);
    }
}
