import {
  Environment,
  EventName,
  Paddle,
  SubscriptionCreatedEvent,
  SubscriptionUpdatedEvent,
  TransactionCompletedEvent,
} from '@paddle/paddle-node-sdk';
import bodyParser from 'body-parser';
import { getUserById } from '../controllers/users.js';
import { sendDiscordPaymentErrorMessage } from '../cron/notification.js';
import { query, transaction } from '../server-utils/query.js';
import { declareHandler } from '../server-utils/routesHandler.js';
import trackServerEvent, { ActivityType } from '../utils/trackServerEvent.js';
import { sendIndieWelcomeEmailToUserId, sendProWelcomeEmailToUserId } from './sendgrid.js';
import Sentry from './sentry.js';

const serverUri = process.env.SERVER_URI || 'http://localhost:3001';

const paddle = new Paddle(process.env.PADDLE_API_KEY, {
  environment: process.env.ENVIRONMENT === 'production' ? Environment.production : Environment.sandbox,
});

export const resolvePlanIdWithPaddle = async (priceId: string, isTrial: boolean) => {
  if (priceId === process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID) {
    return await query(`SELECT id FROM plans WHERE paddle_price_id_env_var = $1 AND is_trial = $2`, [
      'PADDLE_PRO_SUBSCRIPTION_PRICE_ID',
      isTrial,
    ]).then((res) => res.rows[0].id);
  } else if (priceId === process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID) {
    return await query(`SELECT id FROM plans WHERE paddle_price_id_env_var = $1`, [
      'PADDLE_INDIE_SUBSCRIPTION_PRICE_ID',
    ]).then((res) => res.rows[0].id);
  } else {
    throw new Error(`Could not resolve plan id for price id ${priceId}`);
  }
};

const handleSubscriptionCreated = async (res: any, eventData: SubscriptionCreatedEvent) => {
  await transaction(async (query) => {
    const { customerId, items } = eventData.data;
    const subscriptionCustomData = eventData.data.customData as { user_id: number; user_email: string };
    const subscription = items[0];

    try {
      let user;
      const userResult = await query(`SELECT * FROM users WHERE paddle_customer_id = $1`, [customerId]);
      user = userResult?.rows?.[0];
      if (!user) {
        const userResult = await query(`SELECT * FROM users WHERE id = $1`, [subscriptionCustomData.user_id]);
        user = userResult?.rows?.[0];
      }
      if (!user) {
        throw new Error(`User with ${subscriptionCustomData.user_id || customerId} not found.`);
      }

      if (subscription.price.id === process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID) {
        await sendProWelcomeEmailToUserId(user.email, user.display_name);
      } else if (subscription.price.id === process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID) {
        await sendIndieWelcomeEmailToUserId(user.email, user.display_name);
      }

      const { trial } = await getUserById(user.id);

      trackServerEvent(user.id, {
        name: ActivityType.PaddleSubscriptionCreated,
        properties: {
          plan: subscription.price.id === process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID ? 'Pro' : 'Indie',
          isTrial: trial,
        },
      });
    } catch (e) {
      Sentry.captureException(e);
      console.error(e);
      res.status(500).send(`Webhook Error: ${e.message}`);
      return;
    }
  });
};

const handleSubscriptionUpdated = async (res: any, eventData: SubscriptionUpdatedEvent) => {
  try {
    const { customerId, status, items } = eventData.data;
    const subscriptionCustomData = eventData.data.customData as { user_id: number; user_email: string };
    const subscription = items[0];
    const priceCustomData = subscription.price.customData as { plan_id: string };

    let userId = await query(`SELECT id FROM users WHERE paddle_customer_id = $1`, [customerId]).then(
      (res) => res.rows[0].id
    );
    if (!userId && subscriptionCustomData) userId = subscriptionCustomData.user_id;

    if (!userId) {
      throw new Error(
        `User with metadata user id "${subscriptionCustomData.user_id}" or customer id "${customerId}" not found.`
      );
    }

    const tier =
      priceCustomData.plan_id === process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID
        ? 'Pro'
        : priceCustomData.plan_id === process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID
        ? 'Indie'
        : undefined;

    trackServerEvent(userId, {
      name: ActivityType.PaddleSubscriptionUpdated,
      properties: {
        previousPlan: tier,
        change: status,
      },
    });

    if (status === 'canceled') {
      const userResult = await query(
        `
                SELECT
                  u.email,
                  u.display_name,
                  su.subscribed_until
                FROM users u
                JOIN subscription_updates su ON u.id = su.user_id
                WHERE u.id = $1
                ORDER BY su.created_at DESC LIMIT 1
              `,
        [userId]
      );

      // sendCancellationConfirmationEmailToUser(email, firstName || displayName, subscribedUntil, tier);
      // sendFoundersCancellationOutreachEmailToUser(email, firstName || displayName, userId);
    }
  } catch (e) {
    Sentry.captureException(e);
    console.error(e);
    res.status(500).send(`Webhook Error: ${e.message}`);
    return;
  }
};

const handleTransactionCompleted = async (res: any, eventData: TransactionCompletedEvent) => {
  // check in the last 30 minutes if any other transactions were completed

  const { user_id: userId, user_email: userEmail } = eventData.data.customData as {
    user_id: number;
    user_email: string;
  };
  setTimeout(async () => {
    const result = await query(
      `
      SELECT *
      FROM paddle_webhooks_events
      WHERE user_id = $1
      AND type = 'transaction.completed'
      AND created_at >= NOW() - INTERVAL '30 minutes';
      `,
      [userId]
    );

    if (result.rows.length > 1) {
      const errorMsg = `User completed multiple transactions within 30 minutes: ${userEmail} [ID: ${userId}]`;
      Sentry.captureMessage(errorMsg);
      await sendDiscordPaymentErrorMessage(errorMsg);
    }
  }, 5000); // wait for original transaction to be recorded in db before checking for all transactions
};

export const handlePaddleWebhook = declareHandler({
  middleware: bodyParser.raw({ type: '*/*' }),
  func: async (req, res) => {
    const signature = (req.headers['paddle-signature'] as string) || '';
    const rawRequestBody = req.body.toString();
    const secretKey = process.env.PADDLE_WEBHOOK_SECRET_KEY || '';

    try {
      if (signature && rawRequestBody) {
        // The `unmarshal` function will validate the integrity of the webhook and return an entity
        const eventData = paddle.webhooks.unmarshal(rawRequestBody, secretKey, signature);

        res.sendStatus(200);

        await transaction(async (query) => {
          try {
            const { customerId, customData } = eventData.data as any;

            // do some admin. if user id is in custom data, ensure that customer id is stored in db.
            // if user id is not in custom data, attempt to resolve user id from customer id.
            let userId = customData?.user_id;
            const userIdQuery = await query(`SELECT id FROM users WHERE paddle_customer_id = $1`, [customerId]);
            if (userIdQuery.rows.length > 0) {
              userId = userIdQuery.rows[0].id;
            } else {
              if (customData?.user_id)
                await query(`UPDATE users SET paddle_customer_id = $1 WHERE id = $2`, [customerId, customData.user_id]);
            }

            await query(
              `INSERT INTO paddle_webhooks_events (paddle_customer_id, user_id, type, payload) VALUES ($1, $2, $3, $4)`,
              [customerId, userId, eventData.eventType, eventData]
            );
          } catch (e) {
            Sentry.captureException(e);
            console.error(e);
            res.status(500).send(`Error recording Paddle webhook: ${e.message}`);
            return;
          }
        });

        // Handle the event
        switch (eventData.eventType) {
          case EventName.SubscriptionUpdated:
            await handleSubscriptionUpdated(res, eventData as SubscriptionUpdatedEvent);
            break;
          case EventName.SubscriptionCreated:
            await handleSubscriptionCreated(res, eventData as SubscriptionCreatedEvent);
            break;
          case EventName.TransactionCompleted:
            await handleTransactionCompleted(res, eventData as TransactionCompletedEvent);
            break;
        }
      }
    } catch (e) {
      Sentry.captureException(e);
      console.log(e);
    }
  },
});

export const paddleCheckoutSession = declareHandler({
  func: async (req, res) => {
    try {
      if (!req.user) {
        return res.status(401).send({ error: 'Unauthorized - you may need to log in again!' });
      }

      // handle the checkout session creation (plan, upgrade, downgrade, etc)
      const { plan } = req.body as { plan: 'Pro' | 'Indie' | 'Pro (Trial)' };

      let priceId, discountCode;
      if (plan === 'Pro' || 'Pro (Trial)') {
        priceId = process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID;
      }
      if (plan === 'Indie') {
        priceId = process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID;
      }

      const urlParams = new URLSearchParams();
      urlParams.append('priceId', priceId);
      urlParams.append('plan', plan);
      if (!!discountCode) urlParams.append('discountCode', discountCode);
      const url = serverUri + `/checkout?${urlParams.toString()}`;

      res.status(200).send({ url });
    } catch (e) {
      Sentry.captureException(e);
      console.error(e);
    }
  },
});

export const paddleCancellationSession = declareHandler({
  func: async (req, res) => {
    if (!req.user) {
      return res.status(401).send({ error: 'Unauthorized - you may need to log in again!' });
    }

    const paddleQuery = await query(
      `
      SELECT * 
      FROM paddle_webhooks_events 
      WHERE user_id = $1 AND type LIKE 'subscription.%'
      ORDER BY created_at DESC
      LIMIT 1
      `,
      [req.user.id]
    );
    const prevSubscription = paddleQuery.rows[0].payload as SubscriptionCreatedEvent | SubscriptionUpdatedEvent;

    if (!prevSubscription) {
      return res.status(401).send({
        error: 'There was an error retrieving your subscription information. Please contact hello@wavtool.com!',
      });
    }

    const subscription = await paddle.subscriptions.get(prevSubscription.data.id);
    const url = subscription.managementUrls.cancel;
    res.status(200).send({ url });
  },
});

const getPriceIdFromPlan = (plan: 'Pro' | 'Indie' | 'Pro (Trial)') => {
  if (plan === 'Pro' || plan === 'Pro (Trial)') return process.env.PADDLE_PRO_SUBSCRIPTION_PRICE_ID;
  if (plan === 'Indie') return process.env.PADDLE_INDIE_SUBSCRIPTION_PRICE_ID;
  throw new Error('Invalid plan');
};

export const paddleUpdateSubscription = declareHandler({
  func: async (req, res) => {
    const { plan } = req.body;
    const paddleQuery = await query(
      `
      SELECT * 
      FROM paddle_webhooks_events 
      WHERE user_id = $1 AND type LIKE 'subscription.%'
      ORDER BY created_at DESC
      LIMIT 1
      `,
      [req.user.id]
    );
    const prevSubscription = paddleQuery.rows[0].payload as SubscriptionCreatedEvent | SubscriptionUpdatedEvent;

    if (!prevSubscription) {
      return res.status(401).send({ error: 'There was an error retrieving your subscription information.' });
    }

    const priceId = getPriceIdFromPlan(plan);
    const subscription = await paddle.subscriptions.update(prevSubscription.data.id, {
      prorationBillingMode: 'prorated_immediately',
      items: [{ priceId, quantity: 1 }],
    });

    res.status(200).send({ success: true, subscription });
  },
});

export const paddleUncancelSubscription = declareHandler({
  func: async (req, res) => {
    const paddleQuery = await query(
      `
      SELECT * 
      FROM paddle_webhooks_events 
      WHERE user_id = $1 AND type LIKE 'subscription.%'
      ORDER BY created_at DESC
      LIMIT 1
      `,
      [req.user.id]
    );
    const prevSubscription = paddleQuery.rows[0].payload as SubscriptionCreatedEvent | SubscriptionUpdatedEvent;

    if (!prevSubscription) {
      return res.status(401).send({ error: 'There was an error retrieving your subscription information.' });
    }

    if (!prevSubscription.data.scheduledChange) {
      return res.status(401).send({ error: 'Subscription is not scheduled to cancel.' });
    }

    const subscription = await paddle.subscriptions.update(prevSubscription.data.id, { scheduledChange: null });

    res.status(200).send({ success: true, subscription });
  },
});

export const paddleCancelSubscription = declareHandler({
  func: async (req, res) => {
    const paddleQuery = await query(
      `
      SELECT * 
      FROM paddle_webhooks_events 
      WHERE user_id = $1 AND type LIKE 'subscription.%'
      ORDER BY created_at DESC
      LIMIT 1
      `,
      [req.user.id]
    );
    const prevSubscription = paddleQuery.rows[0].payload as SubscriptionCreatedEvent | SubscriptionUpdatedEvent;

    if (!prevSubscription) {
      return res.status(401).send({ error: 'There was an error retrieving your subscription information.' });
    }

    if (prevSubscription.data.scheduledChange !== null) {
      return res.status(406).send({ error: 'Your subscription is already scheduled to cancel.' });
    }

    await paddle.subscriptions.cancel(prevSubscription.data.id, {
      effectiveFrom: 'next_billing_period',
    });

    res.status(200).send({ success: true });
  },
});

export const handleCancellationReasons = declareHandler({
  func: async (req, res) => {
    const { feedback, comment } = req.body;
    await query(
      `
        INSERT INTO subscription_cancellation_details
        (user_id, reason, feedback, comment, status)
        VALUES
        ($1, 'cancellation_requested', $2, $3, 'finalised')
      `,
      [req.user.id, feedback, comment === '' ? null : comment]
    );

    res.status(200).send({ success: true });
  },
});
