package de.cannamanage.service;

import com.stripe.Stripe;
import com.stripe.exception.SignatureVerificationException;
import com.stripe.exception.StripeException;
import com.stripe.model.*;
import com.stripe.model.checkout.Session;
import com.stripe.net.Webhook;
import com.stripe.param.CustomerCreateParams;
import com.stripe.param.checkout.SessionCreateParams;
import de.cannamanage.domain.entity.Club;
import de.cannamanage.domain.entity.Subscription;
import de.cannamanage.domain.entity.TenantContext;
import de.cannamanage.domain.enums.PlanTier;
import de.cannamanage.domain.enums.SubscriptionStatus;
import de.cannamanage.service.repository.ClubRepository;
import de.cannamanage.service.repository.SubscriptionRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.Optional;
import java.util.UUID;

@Slf4j
@Service
@RequiredArgsConstructor
public class StripeService {

    private final SubscriptionRepository subscriptionRepository;
    private final ClubRepository clubRepository;

    @Value("${stripe.secret-key:}")
    private String stripeSecretKey;

    @Value("${stripe.webhook-secret:}")
    private String webhookSecret;

    @Value("${stripe.starter-price-id:}")
    private String starterPriceId;

    @Value("${stripe.pro-price-id:}")
    private String proPriceId;

    @Value("${app.base-url:http://localhost:3000}")
    private String appBaseUrl;

    @PostConstruct
    void init() {
        if (!stripeSecretKey.isBlank()) {
            Stripe.apiKey = stripeSecretKey;
            log.info("Stripe API initialized");
        } else {
            log.warn("Stripe API key not configured — billing features disabled");
        }
    }

    /**
     * Get the current subscription for a club.
     */
    public Optional<Subscription> getSubscription(UUID clubId) {
        return subscriptionRepository.findByClubId(clubId);
    }

    /**
     * Create a Stripe customer for a club and initialize a trial subscription record.
     */
    @Transactional
    public Subscription createTrialSubscription(UUID clubId) throws StripeException {
        Club club = clubRepository.findById(clubId)
                .orElseThrow(() -> new IllegalArgumentException("Club not found: " + clubId));

        // Create Stripe customer
        CustomerCreateParams customerParams = CustomerCreateParams.builder()
                .setEmail(club.getContactEmail())
                .setName(club.getName())
                .putMetadata("clubId", clubId.toString())
                .putMetadata("tenantId", club.getTenantId().toString())
                .build();

        Customer customer = Customer.create(customerParams);

        // Create local subscription record
        Subscription subscription = new Subscription();
        subscription.setClubId(clubId);
        subscription.setTenantId(club.getTenantId());
        subscription.setStripeCustomerId(customer.getId());
        subscription.setPlanTier(PlanTier.TRIAL);
        subscription.setMemberLimit(500);
        subscription.setStatus(SubscriptionStatus.TRIALING);
        subscription.setTrialEndsAt(Instant.now().plusSeconds(90L * 24 * 60 * 60)); // 3 months

        return subscriptionRepository.save(subscription);
    }

    /**
     * Create a Stripe Checkout Session for the given plan tier.
     */
    public String createCheckoutSession(UUID clubId, PlanTier planTier) throws StripeException {
        Subscription subscription = subscriptionRepository.findByClubId(clubId)
                .orElseThrow(() -> new IllegalArgumentException("No subscription found for club: " + clubId));

        String priceId = switch (planTier) {
            case STARTER -> starterPriceId;
            case PRO -> proPriceId;
            case ENTERPRISE -> throw new IllegalArgumentException("Enterprise plans require custom setup");
            case TRIAL -> throw new IllegalArgumentException("Cannot checkout for trial plan");
        };

        SessionCreateParams params = SessionCreateParams.builder()
                .setCustomer(subscription.getStripeCustomerId())
                .setMode(SessionCreateParams.Mode.SUBSCRIPTION)
                .addLineItem(SessionCreateParams.LineItem.builder()
                        .setPrice(priceId)
                        .setQuantity(1L)
                        .build())
                .setSuccessUrl(appBaseUrl + "/settings/billing?success=true")
                .setCancelUrl(appBaseUrl + "/settings/billing?canceled=true")
                .addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD)
                .addPaymentMethodType(SessionCreateParams.PaymentMethodType.SEPA_DEBIT)
                .addPaymentMethodType(SessionCreateParams.PaymentMethodType.PAYPAL)
                .putMetadata("clubId", clubId.toString())
                .putMetadata("planTier", planTier.name())
                .build();

        Session session = Session.create(params);
        return session.getUrl();
    }

    /**
     * Create a Stripe Billing Portal session for self-service management.
     */
    public String createBillingPortalSession(UUID clubId) throws StripeException {
        Subscription subscription = subscriptionRepository.findByClubId(clubId)
                .orElseThrow(() -> new IllegalArgumentException("No subscription found for club: " + clubId));

        com.stripe.param.billingportal.SessionCreateParams params =
                com.stripe.param.billingportal.SessionCreateParams.builder()
                        .setCustomer(subscription.getStripeCustomerId())
                        .setReturnUrl(appBaseUrl + "/settings/billing")
                        .build();

        com.stripe.model.billingportal.Session session =
                com.stripe.model.billingportal.Session.create(params);
        return session.getUrl();
    }

    /**
     * Handle incoming Stripe webhook events.
     */
    @Transactional
    public void handleWebhook(String payload, String sigHeader) {
        Event event;
        try {
            event = Webhook.constructEvent(payload, sigHeader, webhookSecret);
        } catch (SignatureVerificationException e) {
            log.error("Stripe webhook signature verification failed", e);
            throw new IllegalArgumentException("Invalid webhook signature");
        }

        log.info("Processing Stripe event: {} ({})", event.getType(), event.getId());

        switch (event.getType()) {
            case "checkout.session.completed" -> handleCheckoutCompleted(event);
            case "invoice.paid" -> handleInvoicePaid(event);
            case "invoice.payment_failed" -> handleInvoicePaymentFailed(event);
            case "customer.subscription.deleted" -> handleSubscriptionDeleted(event);
            case "customer.subscription.updated" -> handleSubscriptionUpdated(event);
            default -> log.debug("Unhandled Stripe event type: {}", event.getType());
        }
    }

    private void handleCheckoutCompleted(Event event) {
        Session session = (Session) event.getDataObjectDeserializer()
                .getObject().orElse(null);
        if (session == null) return;

        String clubIdStr = session.getMetadata().get("clubId");
        String planTierStr = session.getMetadata().get("planTier");
        if (clubIdStr == null || planTierStr == null) return;

        UUID clubId = UUID.fromString(clubIdStr);
        PlanTier planTier = PlanTier.valueOf(planTierStr);

        subscriptionRepository.findByClubId(clubId).ifPresent(sub -> {
            sub.setStripeSubscriptionId(session.getSubscription());
            sub.setPlanTier(planTier);
            sub.setMemberLimit(getMemberLimit(planTier));
            sub.setStatus(SubscriptionStatus.ACTIVE);
            sub.setCurrentPeriodStart(Instant.now());
            sub.setCurrentPeriodEnd(Instant.now().plusSeconds(30L * 24 * 60 * 60));
            subscriptionRepository.save(sub);
            log.info("Subscription activated for club {} — plan: {}", clubId, planTier);
        });
    }

    private void handleInvoicePaid(Event event) {
        Invoice invoice = (Invoice) event.getDataObjectDeserializer()
                .getObject().orElse(null);
        if (invoice == null || invoice.getSubscription() == null) return;

        subscriptionRepository.findByStripeSubscriptionId(invoice.getSubscription())
                .ifPresent(sub -> {
                    sub.setStatus(SubscriptionStatus.ACTIVE);
                    sub.setCurrentPeriodStart(Instant.ofEpochSecond(invoice.getPeriodStart()));
                    sub.setCurrentPeriodEnd(Instant.ofEpochSecond(invoice.getPeriodEnd()));
                    subscriptionRepository.save(sub);
                    log.info("Invoice paid for subscription: {}", invoice.getSubscription());
                });
    }

    private void handleInvoicePaymentFailed(Event event) {
        Invoice invoice = (Invoice) event.getDataObjectDeserializer()
                .getObject().orElse(null);
        if (invoice == null || invoice.getSubscription() == null) return;

        subscriptionRepository.findByStripeSubscriptionId(invoice.getSubscription())
                .ifPresent(sub -> {
                    sub.setStatus(SubscriptionStatus.PAST_DUE);
                    subscriptionRepository.save(sub);
                    log.warn("Payment failed for subscription: {}", invoice.getSubscription());
                });
    }

    private void handleSubscriptionDeleted(Event event) {
        com.stripe.model.Subscription stripeSub = (com.stripe.model.Subscription) event.getDataObjectDeserializer()
                .getObject().orElse(null);
        if (stripeSub == null) return;

        subscriptionRepository.findByStripeSubscriptionId(stripeSub.getId())
                .ifPresent(sub -> {
                    sub.setStatus(SubscriptionStatus.CANCELED);
                    sub.setCanceledAt(Instant.now());
                    subscriptionRepository.save(sub);
                    log.info("Subscription canceled: {}", stripeSub.getId());
                });
    }

    private void handleSubscriptionUpdated(Event event) {
        com.stripe.model.Subscription stripeSub = (com.stripe.model.Subscription) event.getDataObjectDeserializer()
                .getObject().orElse(null);
        if (stripeSub == null) return;

        subscriptionRepository.findByStripeSubscriptionId(stripeSub.getId())
                .ifPresent(sub -> {
                    // Update period dates
                    sub.setCurrentPeriodStart(Instant.ofEpochSecond(stripeSub.getCurrentPeriodStart()));
                    sub.setCurrentPeriodEnd(Instant.ofEpochSecond(stripeSub.getCurrentPeriodEnd()));

                    // Map Stripe status
                    String stripeStatus = stripeSub.getStatus();
                    switch (stripeStatus) {
                        case "active" -> sub.setStatus(SubscriptionStatus.ACTIVE);
                        case "past_due" -> sub.setStatus(SubscriptionStatus.PAST_DUE);
                        case "canceled" -> sub.setStatus(SubscriptionStatus.CANCELED);
                        case "unpaid" -> sub.setStatus(SubscriptionStatus.UNPAID);
                        case "trialing" -> sub.setStatus(SubscriptionStatus.TRIALING);
                    }
                    subscriptionRepository.save(sub);
                    log.info("Subscription updated: {} → status={}", stripeSub.getId(), stripeStatus);
                });
    }

    private int getMemberLimit(PlanTier tier) {
        return switch (tier) {
            case TRIAL -> 500;
            case STARTER -> 30;
            case PRO -> 100;
            case ENTERPRISE -> 500;
        };
    }
}
