import type { FastifyInstance, FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
import type Stripe from 'stripe';
import { getPlans, getPlanById, getPlanByPriceId } from '../plugins/stripe';

interface CheckoutBody {
  plan: 'essential' | 'premium';
}

const stripeRoutes: FastifyPluginAsync = async (fastify: FastifyInstance) => {
  const authenticate = async (request: FastifyRequest, reply: FastifyReply) => {
    try {
      await fastify.authenticate(request, reply);
    } catch {
      reply.code(401).send({ error: 'Authentification requise' });
    }
  };

  // -------------------------------------------------------------------------
  // GET /stripe/plans — Liste publique des plans (pas besoin d'auth)
  // -------------------------------------------------------------------------
  fastify.get('/plans', async () => {
    const plans = getPlans().map((plan) => ({
      id: plan.id,
      name: plan.name,
      description: plan.description,
      monthlyRecipes: plan.monthlyRecipes,
      features: plan.features,
      // priceId est omis : info sensible, pas utile au frontend
      available: !!plan.priceId,
    }));
    return { plans };
  });

  // -------------------------------------------------------------------------
  // GET /stripe/subscription — Statut d'abonnement de l'utilisateur courant
  // -------------------------------------------------------------------------
  fastify.get(
    '/subscription',
    { preHandler: authenticate },
    async (request, reply) => {
      try {
        const user = await fastify.prisma.user.findUnique({
          where: { id: request.user.id },
          select: {
            subscription: true,
            subscriptionStatus: true,
            subscriptionPriceId: true,
            subscriptionCurrentPeriodEnd: true,
            stripeSubscriptionId: true,
          },
        });

        if (!user) {
          return reply.code(404).send({ error: 'Utilisateur non trouvé' });
        }

        const plan = user.subscription || 'free';
        return {
          plan,
          status: user.subscriptionStatus ?? (plan === 'free' ? 'none' : null),
          currentPeriodEnd: user.subscriptionCurrentPeriodEnd,
          hasActiveSubscription: !!user.stripeSubscriptionId,
        };
      } catch (err) {
        fastify.log.error(err);
        return reply.code(500).send({ error: 'Erreur lors de la récupération' });
      }
    }
  );

  // -------------------------------------------------------------------------
  // POST /stripe/checkout — Crée une Checkout Session et renvoie l'URL
  // -------------------------------------------------------------------------
  fastify.post<{ Body: CheckoutBody }>(
    '/checkout',
    {
      preHandler: authenticate,
      schema: {
        body: {
          type: 'object',
          required: ['plan'],
          properties: {
            plan: { type: 'string', enum: ['essential', 'premium'] },
          },
        },
      },
    },
    async (request, reply) => {
      if (!fastify.stripe) {
        return reply.code(503).send({ error: 'Stripe n\'est pas configuré' });
      }

      try {
        const planConfig = getPlanById(request.body.plan);
        if (!planConfig || !planConfig.priceId) {
          return reply.code(400).send({
            error: `Plan "${request.body.plan}" non disponible. Vérifiez que STRIPE_PRICE_ID_${request.body.plan.toUpperCase()} est défini.`,
          });
        }

        const user = await fastify.prisma.user.findUnique({
          where: { id: request.user.id },
        });
        if (!user) {
          return reply.code(404).send({ error: 'Utilisateur non trouvé' });
        }

        // Crée le customer Stripe si absent (ancien utilisateurs, Stripe auparavant désactivé)
        let customerId = user.stripeId;
        if (!customerId) {
          if (!fastify.createCustomer) {
            return reply.code(503).send({ error: 'Stripe non initialisé' });
          }
          const customer = await fastify.createCustomer(user.email, user.name);
          customerId = customer.id;
          await fastify.prisma.user.update({
            where: { id: user.id },
            data: { stripeId: customerId },
          });
        }

        const appUrl = process.env.FRONTEND_URL || 'http://localhost:5173';

        const session = await fastify.stripe.checkout.sessions.create({
          mode: 'subscription',
          customer: customerId,
          client_reference_id: user.id,
          line_items: [{ price: planConfig.priceId, quantity: 1 }],
          success_url: `${appUrl}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
          cancel_url: `${appUrl}/pricing`,
          allow_promotion_codes: true,
          billing_address_collection: 'auto',
          // Pas de payment_method_types : on laisse Stripe choisir
          // dynamiquement selon la région de l'utilisateur (meilleure
          // conversion). Active les moyens de paiement depuis le dashboard.
          subscription_data: {
            metadata: {
              userId: user.id,
              plan: planConfig.id,
            },
          },
          metadata: {
            userId: user.id,
            plan: planConfig.id,
          },
        });

        return { url: session.url, sessionId: session.id };
      } catch (err) {
        fastify.log.error(err, 'checkout session creation failed');
        return reply.code(500).send({ error: 'Erreur lors de la création de la session' });
      }
    }
  );

  // -------------------------------------------------------------------------
  // POST /stripe/sync — Resynchronise l'abonnement depuis Stripe
  // -------------------------------------------------------------------------
  // Utile quand le webhook n'est pas arrivé (stripe listen pas démarré,
  // whsec mal configuré, etc.) : le frontend peut appeler cet endpoint
  // après un checkout pour récupérer l'état réel immédiatement.
  fastify.post(
    '/sync',
    { preHandler: authenticate },
    async (request, reply) => {
      if (!fastify.stripe) {
        return reply.code(503).send({ error: 'Stripe n\'est pas configuré' });
      }

      try {
        const user = await fastify.prisma.user.findUnique({
          where: { id: request.user.id },
        });

        if (!user) {
          return reply.code(404).send({ error: 'Utilisateur non trouvé' });
        }
        if (!user.stripeId) {
          return reply.code(400).send({
            error: 'Aucun customer Stripe pour cet utilisateur',
          });
        }

        // Cherche l'abonnement actif (status active ou trialing) du customer
        const subscriptions = await fastify.stripe.subscriptions.list({
          customer: user.stripeId,
          status: 'all',
          limit: 5,
        });

        // Trie par date de création desc pour prendre le plus récent
        const active = subscriptions.data
          .filter((s) => ['active', 'trialing', 'past_due'].includes(s.status))
          .sort((a, b) => b.created - a.created)[0];

        if (!active) {
          // Pas d'abonnement actif → repasse en plan gratuit
          await fastify.prisma.user.update({
            where: { id: user.id },
            data: {
              subscription: 'free',
              subscriptionStatus: null,
              stripeSubscriptionId: null,
              subscriptionPriceId: null,
              subscriptionCurrentPeriodEnd: null,
            },
          });
          return { plan: 'free', status: 'none', synced: true };
        }

        await applySubscriptionToUser(fastify, active, user.id);

        // Relit l'utilisateur après update
        const refreshed = await fastify.prisma.user.findUnique({
          where: { id: user.id },
          select: {
            subscription: true,
            subscriptionStatus: true,
            subscriptionCurrentPeriodEnd: true,
          },
        });

        return {
          plan: refreshed?.subscription ?? 'free',
          status: refreshed?.subscriptionStatus ?? null,
          currentPeriodEnd: refreshed?.subscriptionCurrentPeriodEnd ?? null,
          synced: true,
        };
      } catch (err) {
        fastify.log.error(err, 'stripe sync failed');
        return reply.code(500).send({ error: 'Erreur lors de la synchronisation' });
      }
    }
  );

  // -------------------------------------------------------------------------
  // POST /stripe/portal — Redirige vers le Customer Portal (gestion abo)
  // -------------------------------------------------------------------------
  fastify.post(
    '/portal',
    { preHandler: authenticate },
    async (request, reply) => {
      if (!fastify.stripe) {
        return reply.code(503).send({ error: 'Stripe n\'est pas configuré' });
      }

      try {
        const user = await fastify.prisma.user.findUnique({
          where: { id: request.user.id },
        });

        if (!user || !user.stripeId) {
          return reply.code(400).send({
            error: 'Aucun compte de facturation. Commence par souscrire un abonnement.',
          });
        }

        const appUrl = process.env.FRONTEND_URL || 'http://localhost:5173';

        const portalSession = await fastify.stripe.billingPortal.sessions.create({
          customer: user.stripeId,
          return_url: `${appUrl}/profile`,
        });

        return { url: portalSession.url };
      } catch (err) {
        fastify.log.error(err, 'portal session creation failed');
        return reply.code(500).send({ error: 'Erreur lors de la création du portail' });
      }
    }
  );

  // -------------------------------------------------------------------------
  // POST /stripe/portal/cancel — Portail deep-linké sur le flow d'annulation
  // -------------------------------------------------------------------------
  // Contrairement à /portal qui ouvre le portail en mode "libre", celui-ci
  // ouvre directement l'écran de confirmation d'annulation pour la
  // subscription active de l'utilisateur, et redirige automatiquement
  // vers /subscription/cancelled après validation.
  fastify.post(
    '/portal/cancel',
    { preHandler: authenticate },
    async (request, reply) => {
      if (!fastify.stripe) {
        return reply.code(503).send({ error: 'Stripe n\'est pas configuré' });
      }

      try {
        const user = await fastify.prisma.user.findUnique({
          where: { id: request.user.id },
        });

        if (!user || !user.stripeId) {
          return reply.code(400).send({
            error: 'Aucun compte de facturation Stripe associé',
          });
        }
        if (!user.stripeSubscriptionId) {
          return reply.code(400).send({
            error: 'Aucun abonnement actif à annuler',
          });
        }

        const appUrl = process.env.FRONTEND_URL || 'http://localhost:5173';

        const portalSession = await fastify.stripe.billingPortal.sessions.create({
          customer: user.stripeId,
          // Return URL pour le bouton "retour" classique du portail
          return_url: `${appUrl}/profile`,
          // Deep-link dans le flow d'annulation avec auto-redirect
          flow_data: {
            type: 'subscription_cancel',
            subscription_cancel: {
              subscription: user.stripeSubscriptionId,
            },
            after_completion: {
              type: 'redirect',
              redirect: {
                return_url: `${appUrl}/subscription/cancelled`,
              },
            },
          },
        });

        return { url: portalSession.url };
      } catch (err) {
        fastify.log.error(err, 'cancel portal session creation failed');
        return reply.code(500).send({ error: "Erreur lors de l'ouverture du portail" });
      }
    }
  );

  // -------------------------------------------------------------------------
  // POST /stripe/webhook — Receiver d'événements Stripe (signature vérifiée)
  // -------------------------------------------------------------------------
  fastify.post('/webhook', async (request, reply) => {
    if (!fastify.stripe) {
      return reply.code(503).send({ error: 'Stripe non configuré' });
    }

    const signature = request.headers['stripe-signature'];
    const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

    if (!signature || typeof signature !== 'string') {
      return reply.code(400).send({ error: 'Signature manquante' });
    }
    if (!webhookSecret) {
      fastify.log.error('STRIPE_WEBHOOK_SECRET non défini');
      return reply.code(500).send({ error: 'Webhook non configuré' });
    }
    if (!request.rawBody) {
      fastify.log.error('Raw body non disponible — vérifier le content-type parser');
      return reply.code(400).send({ error: 'Raw body indisponible' });
    }

    let event: Stripe.Event;
    try {
      event = fastify.stripe.webhooks.constructEvent(
        request.rawBody,
        signature,
        webhookSecret
      );
    } catch (err) {
      fastify.log.warn(`Webhook signature invalide: ${(err as Error).message}`);
      return reply.code(400).send({ error: 'Signature invalide' });
    }

    fastify.log.info({ type: event.type, id: event.id }, 'stripe_webhook_received');

    try {
      switch (event.type) {
        case 'checkout.session.completed': {
          const session = event.data.object as Stripe.Checkout.Session;
          // La session de checkout est terminée. On attrape tout de suite
          // la subscription associée pour avoir le priceId + dates.
          if (session.mode === 'subscription' && session.subscription) {
            const subscriptionId =
              typeof session.subscription === 'string'
                ? session.subscription
                : session.subscription.id;
            const subscription = await fastify.stripe.subscriptions.retrieve(subscriptionId);
            await applySubscriptionToUser(fastify, subscription, session.client_reference_id);
          }
          break;
        }

        case 'customer.subscription.created':
        case 'customer.subscription.updated': {
          const subscription = event.data.object as Stripe.Subscription;
          await applySubscriptionToUser(fastify, subscription, null);
          break;
        }

        case 'customer.subscription.deleted': {
          const subscription = event.data.object as Stripe.Subscription;
          await fastify.prisma.user.updateMany({
            where: { stripeSubscriptionId: subscription.id },
            data: {
              subscription: 'free',
              subscriptionStatus: 'canceled',
              stripeSubscriptionId: null,
              subscriptionPriceId: null,
              subscriptionCurrentPeriodEnd: null,
            },
          });
          break;
        }

        case 'invoice.payment_failed': {
          const invoice = event.data.object as Stripe.Invoice;
          const subscriptionId =
            typeof invoice.subscription === 'string' ? invoice.subscription : null;
          if (subscriptionId) {
            await fastify.prisma.user.updateMany({
              where: { stripeSubscriptionId: subscriptionId },
              data: { subscriptionStatus: 'past_due' },
            });
          }
          break;
        }

        default:
          // On ignore silencieusement les événements qui ne nous intéressent pas
          break;
      }
    } catch (err) {
      fastify.log.error(err, `webhook handler failed for ${event.type}`);
      // On renvoie 500 pour que Stripe retente — sauf si c'est une erreur
      // de validation métier qui ne sera pas résolue par un retry
      return reply.code(500).send({ error: 'Handler failed' });
    }

    return { received: true };
  });
};

/**
 * Applique l'état d'une Subscription Stripe sur l'utilisateur correspondant.
 * Résout l'utilisateur via (dans l'ordre) :
 *   1. `clientReferenceId` (posé par checkout.session.completed)
 *   2. `subscription.metadata.userId`
 *   3. `customer` (stripeId)
 */
async function applySubscriptionToUser(
  fastify: FastifyInstance,
  subscription: Stripe.Subscription,
  clientReferenceId: string | null
): Promise<void> {
  const customerId =
    typeof subscription.customer === 'string'
      ? subscription.customer
      : subscription.customer.id;

  const priceId = subscription.items.data[0]?.price.id;
  const plan = priceId ? getPlanByPriceId(priceId) : undefined;

  let userId: string | null = clientReferenceId;
  if (!userId && subscription.metadata?.userId) {
    userId = subscription.metadata.userId;
  }

  const data = {
    stripeSubscriptionId: subscription.id,
    subscriptionStatus: subscription.status,
    subscriptionPriceId: priceId ?? null,
    subscriptionCurrentPeriodEnd: new Date(subscription.current_period_end * 1000),
    subscription: plan?.id ?? 'essential', // fallback safe
    stripeId: customerId,
  };

  if (userId) {
    await fastify.prisma.user.update({ where: { id: userId }, data }).catch(async () => {
      // L'utilisateur a pu être supprimé entre-temps, ou l'ID est invalide.
      // On retombe sur une résolution par customerId.
      await fastify.prisma.user.updateMany({
        where: { stripeId: customerId },
        data,
      });
    });
  } else {
    await fastify.prisma.user.updateMany({
      where: { stripeId: customerId },
      data,
    });
  }
}

export default stripeRoutes;
