using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Stripe;
using Stripe.Checkout;
using TrackQrApi.Data;
using TrackQrApi.Features.Billing.Settings;
using TrackQrApi.Models;

namespace TrackQrApi.Features.Billing.Services;

public interface IStripeService
{
    Task<string> CreateCheckoutSessionAsync(Guid userId, Guid workspaceId, WorkspacePlan plan, string successUrl,
        string cancelUrl, CancellationToken ct = default);

    Task<string> CreateCustomerPortalSessionAsync(Guid userId, string returnUrl, CancellationToken ct = default);
    Task<Subscription?> GetSubscriptionAsync(string subscriptionId, CancellationToken ct = default);
    Task CancelSubscriptionAsync(string subscriptionId, CancellationToken ct = default);
    Task HandleCheckoutCompletedAsync(Session session, CancellationToken ct = default);
    Task HandleSubscriptionUpdatedAsync(Subscription subscription, CancellationToken ct = default);
    Task HandleSubscriptionDeletedAsync(Subscription subscription, CancellationToken ct = default);
    string GetPriceIdForPlan(WorkspacePlan plan);
    WorkspacePlan GetPlanForPriceId(string priceId);
}

public class StripeService : IStripeService
{
    private readonly ILogger<StripeService> _logger;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly StripeSettings _settings;

    public StripeService(
        IServiceScopeFactory scopeFactory,
        IOptions<StripeSettings> settings,
        ILogger<StripeService> logger)
    {
        _scopeFactory = scopeFactory;
        _settings = settings.Value;
        _logger = logger;

        StripeConfiguration.ApiKey = _settings.SecretKey;
    }

    public async Task<string> CreateCheckoutSessionAsync(
        Guid userId,
        Guid workspaceId,
        WorkspacePlan plan,
        string successUrl,
        string cancelUrl,
        CancellationToken ct = default)
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var user = await db.Users.FindAsync([userId], ct)
                   ?? throw new InvalidOperationException("User not found");

        // Get or create Stripe customer
        var customerId = user.StripeCustomerId;
        if (string.IsNullOrEmpty(customerId))
        {
            var customerService = new CustomerService();
            var customer = await customerService.CreateAsync(new CustomerCreateOptions
            {
                Email = user.Email,
                Metadata = new Dictionary<string, string>
                {
                    ["user_id"] = userId.ToString()
                }
            }, cancellationToken: ct);

            customerId = customer.Id;
            user.StripeCustomerId = customerId;
            await db.SaveChangesAsync(ct);
        }

        var priceId = GetPriceIdForPlan(plan);
        if (string.IsNullOrEmpty(priceId)) throw new InvalidOperationException($"No price configured for plan: {plan}");

        var sessionService = new SessionService();
        var session = await sessionService.CreateAsync(new SessionCreateOptions
        {
            Customer = customerId,
            Mode = "subscription",
            PaymentMethodTypes = ["card"],
            LineItems =
            [
                new SessionLineItemOptions
                {
                    Price = priceId,
                    Quantity = 1
                }
            ],
            SuccessUrl = successUrl + "?session_id={CHECKOUT_SESSION_ID}",
            CancelUrl = cancelUrl,
            Metadata = new Dictionary<string, string>
            {
                ["user_id"] = userId.ToString(),
                ["workspace_id"] = workspaceId.ToString(),
                ["plan"] = plan.ToString()
            },
            SubscriptionData = new SessionSubscriptionDataOptions
            {
                Metadata = new Dictionary<string, string>
                {
                    ["user_id"] = userId.ToString(),
                    ["workspace_id"] = workspaceId.ToString()
                }
            }
        }, cancellationToken: ct);

        return session.Url;
    }

    public async Task<string> CreateCustomerPortalSessionAsync(
        Guid userId,
        string returnUrl,
        CancellationToken ct = default)
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var user = await db.Users.FindAsync([userId], ct)
                   ?? throw new InvalidOperationException("User not found");

        if (string.IsNullOrEmpty(user.StripeCustomerId))
            throw new InvalidOperationException("User has no Stripe customer");

        var sessionService = new Stripe.BillingPortal.SessionService();
        var session = await sessionService.CreateAsync(new Stripe.BillingPortal.SessionCreateOptions
        {
            Customer = user.StripeCustomerId,
            ReturnUrl = returnUrl
        }, cancellationToken: ct);

        return session.Url;
    }

    public async Task<Subscription?> GetSubscriptionAsync(string subscriptionId, CancellationToken ct = default)
    {
        var service = new SubscriptionService();
        try
        {
            return await service.GetAsync(subscriptionId, cancellationToken: ct);
        }
        catch (StripeException ex) when (ex.StripeError?.Code == "resource_missing")
        {
            return null;
        }
    }

    public async Task CancelSubscriptionAsync(string subscriptionId, CancellationToken ct = default)
    {
        var service = new SubscriptionService();
        await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions
        {
            InvoiceNow = false,
            Prorate = false
        }, cancellationToken: ct);
    }

    public async Task HandleCheckoutCompletedAsync(Session session, CancellationToken ct = default)
    {
        var workspaceIdStr = session.Metadata.GetValueOrDefault("workspace_id");
        var planStr = session.Metadata.GetValueOrDefault("plan");

        if (string.IsNullOrEmpty(workspaceIdStr) || string.IsNullOrEmpty(planStr))
        {
            _logger.LogWarning("Checkout session missing metadata: {SessionId}", session.Id);
            return;
        }

        if (!Guid.TryParse(workspaceIdStr, out var workspaceId))
        {
            _logger.LogWarning("Invalid workspace_id in checkout session: {WorkspaceId}", workspaceIdStr);
            return;
        }

        if (!Enum.TryParse<WorkspacePlan>(planStr, out var plan))
        {
            _logger.LogWarning("Invalid plan in checkout session: {Plan}", planStr);
            return;
        }

        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var workspace = await db.Workspaces.FindAsync([workspaceId], ct);
        if (workspace == null)
        {
            _logger.LogWarning("Workspace not found for checkout: {WorkspaceId}", workspaceId);
            return;
        }

        workspace.Plan = plan;
        workspace.StripeSubscriptionId = session.SubscriptionId;

        // Get subscription to set end date
        if (!string.IsNullOrEmpty(session.SubscriptionId))
        {
            var subscription = await GetSubscriptionAsync(session.SubscriptionId, ct);
            if (subscription != null) workspace.SubscriptionEndsAt = subscription.CurrentPeriodEnd;
        }

        await db.SaveChangesAsync(ct);

        _logger.LogInformation(
            "Workspace {WorkspaceId} upgraded to {Plan} via checkout {SessionId}",
            workspaceId, plan, session.Id);
    }

    public async Task HandleSubscriptionUpdatedAsync(Subscription subscription, CancellationToken ct = default)
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var workspace = await db.Workspaces
            .FirstOrDefaultAsync(w => w.StripeSubscriptionId == subscription.Id, ct);

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

        // Update plan based on price
        var priceId = subscription.Items.Data.FirstOrDefault()?.Price?.Id;
        if (!string.IsNullOrEmpty(priceId))
        {
            var newPlan = GetPlanForPriceId(priceId);
            if (workspace.Plan != newPlan)
            {
                _logger.LogInformation(
                    "Workspace {WorkspaceId} plan changed from {OldPlan} to {NewPlan}",
                    workspace.Id, workspace.Plan, newPlan);
                workspace.Plan = newPlan;
            }
        }

        workspace.SubscriptionEndsAt = subscription.CurrentPeriodEnd;

        // Handle cancellation at period end
        if (subscription.CancelAtPeriodEnd)
            _logger.LogInformation(
                "Workspace {WorkspaceId} subscription will cancel at {EndDate}",
                workspace.Id, subscription.CurrentPeriodEnd);

        await db.SaveChangesAsync(ct);
    }

    public async Task HandleSubscriptionDeletedAsync(Subscription subscription, CancellationToken ct = default)
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        var workspace = await db.Workspaces
            .FirstOrDefaultAsync(w => w.StripeSubscriptionId == subscription.Id, ct);

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

        _logger.LogInformation(
            "Workspace {WorkspaceId} subscription deleted, downgrading to Free",
            workspace.Id);

        workspace.Plan = WorkspacePlan.Free;
        workspace.StripeSubscriptionId = null;
        workspace.SubscriptionEndsAt = null;

        await db.SaveChangesAsync(ct);
    }

    public string GetPriceIdForPlan(WorkspacePlan plan)
    {
        return plan switch
        {
            WorkspacePlan.Pro => _settings.ProPriceId,
            WorkspacePlan.Business => _settings.BusinessPriceId,
            _ => string.Empty
        };
    }

    public WorkspacePlan GetPlanForPriceId(string priceId)
    {
        if (priceId == _settings.ProPriceId)
            return WorkspacePlan.Pro;
        if (priceId == _settings.BusinessPriceId)
            return WorkspacePlan.Business;
        return WorkspacePlan.Free;
    }
}