import { sendResetPasswordEmail } from '../auth/fusionauth.js';
import { sendDiscordDeletionRequestMessage } from '../cron/notification.js';
import { resolvePlanIdWithPaddle } from '../external-services/paddle.js';
import { logger } from '../server-utils/logger.js';
import { query } from '../server-utils/query.js';
import { declareHandler } from '../server-utils/routesHandler.js';
import { PlanGatedFeature, User } from '../types/serverTypes.js';

/**
 * This file contains route handlers for dealing with users
 */

export const getFeaturesForPlan = async (planId: number) => {
  const queryResult = await query(
    `
      SELECT code
      FROM app_features
      JOIN plan_app_features ON app_features.id = plan_app_features.app_feature_id
      WHERE plan_id = $1;
    `,
    [planId]
  );
  return queryResult.rows
    .map((row) => row.code)
    .reduce((acc, code) => ({ ...acc, [code as PlanGatedFeature]: true }), {}) as Partial<
    Record<PlanGatedFeature, true>
  >;
};

export const getUserIdByEmail = async (email): Promise<number> => {
  const result = await query(
    `
      SELECT id as user_id FROM users
      WHERE email = $1;
    `,
    [email]
  );
  if (result.rows.length) {
    return result.rows[0].user_id;
  }
  return null;
};

export const getUserIdByIssuer = async (issuer, externalId): Promise<number> => {
  logger.info('Getting user id by issuer', issuer, externalId);
  const result = await query(
    `
      SELECT user_id FROM user_identities
      WHERE issuer = $1 AND external_id = $2;
    `,
    [issuer, externalId]
  );
  if (result.rows.length) {
    return result.rows[0].user_id;
  }
  return null;
};

const setPlanOverride = async (options: {
  planName: string;
  email: string;
}): Promise<{
  planId: number;
  planName: string;
  isTrial: boolean;
  willRenew: boolean;
  subscribedUntil: Date;
} | null> => {
  const { planName, email } = options;

  function getPlanPrecedenceLevel(plan: string) {
    switch (plan) {
      case 'Pro':
        return 3;
      case 'Pro (Trial)':
        return 2;
      case 'Indie':
        return 1;
      default:
        return 0;
    }
  }

  const userPlanPrecedence = getPlanPrecedenceLevel(planName);
  if (userPlanPrecedence === 3) {
    return null;
  }

  if (email) {
    // Check if this email has a higher plan override than the user's current plan
    const planOverrides = await query(
      `
        SELECT
          plans.id as plan_id,
          plans.name as plan,
          plans.is_trial as is_trial,
          plan_overrides.expires_at
        FROM plan_overrides
        JOIN plans ON plan_overrides.plan_id = plans.id
        WHERE plan_overrides.email = $1 AND plan_overrides.expires_at > NOW() AND plan_overrides.enabled = TRUE
        ORDER BY plan_overrides.expires_at ASC
        LIMIT 1;
      `,
      [email]
    );

    if (planOverrides.rows.length) {
      const planOverride = planOverrides.rows[0];
      if (getPlanPrecedenceLevel(planOverride.plan) > userPlanPrecedence) {
        logger.info('Plan override found for user', email, 'plan_id', planOverride.plan);
        const result = {
          planId: planOverride.plan_id,
          planName: planOverride.plan,
          isTrial: planOverride.is_trial,
          willRenew: false,
          subscribedUntil: planOverride.expires_at,
        };
        return result;
      }
    }
  }

  return null;
};

export const getPaddleSubscription = async (userId: number) => {
  // as long as paddle_customer_id exists, we can assume this is a paddle customer

  // need to get the most recent active subscription. there could be multiple subscriptions, but only one active one
  // given all the subscription events, find the most recent one that is active

  // TODO: steps 1-3 could be simplified into a single db query
  // 1. get all subscriptions events for this user
  const allSubscriptionsQuery = await query(
    `SELECT * FROM paddle_webhooks_events WHERE user_id = $1 AND type LIKE 'subscription.%' ORDER BY created_at DESC;`,
    [userId]
  );

  // 2. figure out which subscriptions exist, and whether they are active or canceled
  // there should only be max 1 active subscription at a time, but we'll handle multiple just in case
  const allSubscriptionsMap = allSubscriptionsQuery.rows.reduce((acc, row) => {
    const { id, status } = row.payload.data;
    if (status === 'canceled') acc[id] = false;
    else acc[id] = acc[id] === false ? false : true;
    return acc;
  }, {});

  // 3. get the first/only relevant subscription id
  const subscriptionId = Object.keys(allSubscriptionsMap).filter((id) => allSubscriptionsMap[id] === true)[0];

  if (!subscriptionId) return null;

  // get the latest possible subscription event and get the subscription data from that
  const prevSubscriptionQuery = await query(
    `SELECT * FROM paddle_webhooks_events WHERE payload->'data'->>'id' = $1 ORDER BY created_at DESC LIMIT 1`,
    [subscriptionId]
  );

  return prevSubscriptionQuery.rows[0].payload;
};

export const getUserById = async (id: number): Promise<User | null> => {
  let planId, planName, willRenew, subscribedUntil, trial, paymentProvider;

  // resolve stripe + paddle subscriptions by looking up stripe_customer_id and paddle_customer_id.
  const userQuery = await query(`SELECT * FROM users WHERE id = $1`, [id]);
  const user = userQuery.rows[0];
  const { stripe_customer_id, paddle_customer_id } = user;

  // TODO: rate-limit this to avoid spamming discord
  // if (stripe_customer_id && paddle_customer_id) {
  //   sendDiscordMonitoringMessage(
  //     `[PAYMENTS ALERT] user ${user.id} (${user.email}) has both Stripe and Paddle IDs. Please investigate and resolve if needed.`,
  //     'Payment monitoring bot'
  //   );
  // }

  if (!paddle_customer_id) {
    const result = await query(
      `
        SELECT
          users.id AS id,
          email,
          display_name,
          role,
          environment,
          subscribed_until,
          plans.name AS plan_name,
          plans.id AS plan_id,
          plans.is_trial AS trial,
          will_renew,
          share_name,
          user_categorisation,
          users.created_at AS created_at
        FROM
          users
        LEFT OUTER JOIN
          subscription_updates ON users.id = subscription_updates.user_id
        LEFT OUTER JOIN
          plans ON plans.id = subscription_updates.plan_id
        WHERE
          users.id = $1
        ORDER BY subscription_updates.created_at DESC LIMIT 1;
      `,
      [id]
    );

    if (result.rows.length === 0) return null;

    const subscriptionExpired = !result.rows[0].subscribed_until || result.rows[0].subscribed_until < new Date();

    planId = subscriptionExpired ? null : result.rows[0].plan_id;
    planName = subscriptionExpired ? null : result.rows[0].plan_name;
    willRenew = subscriptionExpired ? false : result.rows[0].will_renew;
    subscribedUntil = subscriptionExpired ? null : result.rows[0].subscribed_until;
    trial = subscriptionExpired ? false : !!result.rows[0].trial;

    // could have subscription_updates via musehub, check if user has a stripe id before assigning stripe as payment provider
    if (!!stripe_customer_id) paymentProvider = 'stripe';
  } else if (paddle_customer_id) {
    const prevSubscription = await getPaddleSubscription(id);

    if (!!prevSubscription) {
      const { data: prevSubscriptionData } = prevSubscription;
      willRenew = !!prevSubscriptionData?.nextBilledAt;

      // If a user buys one month of pro, then cancels, subscribedUntil should be one month from the date they subscribed
      if (
        prevSubscriptionData.scheduledChange?.action === 'cancel' &&
        prevSubscriptionData.scheduledChange?.resumeAt === null
      ) {
        subscribedUntil = prevSubscriptionData.scheduledChange.effectiveAt;
      } else {
        subscribedUntil = prevSubscriptionData.nextBilledAt;
      }
      const priceId = prevSubscriptionData.items[0].price.id;
      const trialing = prevSubscriptionData.status === 'trialing';

      planId = await resolvePlanIdWithPaddle(priceId, trialing);
      const planQuery = await query(`SELECT name, is_trial FROM plans WHERE id = $1`, [planId]);
      planName = planQuery.rows[0].name;
      trial = planQuery.rows[0].is_trial;
      paymentProvider = 'paddle';
    }
  }

  const originalPlanId = planId;

  const planOverride = await setPlanOverride({ planName, email: user.email });

  if (planOverride) {
    planId = planOverride.planId;
    planName = planOverride.planName;
    willRenew = planOverride.willRenew;
    subscribedUntil = planOverride.subscribedUntil;
    trial = planOverride.isTrial;
  }

  const subscriptionUpdates = await query(
    `
      SELECT user_id FROM paddle_webhooks_events WHERE user_id = $1 AND type ILIKE '%subscription.trialing%'
        UNION
      SELECT user_id FROM subscription_updates WHERE user_id = $1;
    `,
    [id]
  );

  const trialEligible = !trial && !originalPlanId && subscriptionUpdates.rows.length === 0;

  // Uncomment to enable 24 hour trial for 50% of new users.
  // if (id % 2 === 0 && trialEligible && Date.now() < endOfFirst24Hours) {
  //   planId = 2; // pro trial
  //   planName = 'Pro (Trial)';
  //   willRenew = false;
  //   subscribedUntil = new Date(endOfFirst24Hours);
  //   trial = true;
  // }

  const features = planId ? await getFeaturesForPlan(planId) : {};

  return {
    id,
    email: user.email,
    username: user.display_name,
    shareName: user.share_name,
    role: user.role,
    willRenew,
    subscribedUntil,
    userCategorisation: user.user_categorisation,
    plan: planName,
    trialEligible,
    features,
    trial,
    environment: user.environment,
    paymentProvider,
  };
};

export const updateUserCategorisation = declareHandler({
  func: async (req, res) => {
    const { categorisation } = req.body;
    const userId = req.user.id;
    await query(`UPDATE users SET user_categorisation = $1 WHERE id = $2`, [categorisation, userId]);
    res.send({ success: true });
  },
});

export const updateLanguage = declareHandler({
  func: async (req, res) => {
    const { language, type } = req.body;
    try {
      if (type === 'browser') {
        const userId = req.user.id;
        await query(`UPDATE users SET browser_language = $1 WHERE id = $2`, [language, userId]);
        res.send({ success: true });
      } else {
        res.send({ success: false });
      }
    } catch (e) {
      res.send({ success: false });
    }
  },
});

export const getLanguage = declareHandler({
  func: async (req, res) => {
    const userId = req.user.id;
    const result = await query(`SELECT browser_language FROM users WHERE id = $1`, [userId]);
    res.send({ language: result.rows[0].browser_language });
  },
});

export const setUsername = declareHandler({
  func: async (req, res) => {
    const { username } = req.body;
    const userId = req.user.id;
    await query(`UPDATE users SET display_name = $1 WHERE id = $2`, [username, userId]);
    res.send({ success: true });
  },
});

export const setSharename = declareHandler({
  func: async (req, res) => {
    const { sharename } = req.body;
    const userId = req.user.id;
    await query(`UPDATE users SET share_name = $1 WHERE id = $2`, [sharename, userId]);
    res.send({ success: true });
  },
});

export const getUserIdentities = declareHandler({
  func: async (req, res) => {
    const userId = req.user.id;
    const result = await query(
      `
      SELECT
        issuer
      FROM
        user_identities
      WHERE
        user_id = $1;
    `,
      [userId]
    );

    const identities = {
      google: false,
      facebook: false,
      email: false,
    };
    for (const row of result.rows) {
      console.log(row.issuer);
      if (row.issuer.includes('google')) {
        identities.google = true;
      }
      if (row.issuer.includes('facebook')) {
        identities.facebook = true;
      }
      if (row.issuer.includes('fusionauth')) {
        identities.email = true;
      }
    }
    res.send(identities);
  },
});

/**
 * This sends the reset password email to the user and is used by users who are already logged in.
 */
export const requestPasswordResetHandler = declareHandler({
  func: async (req, res) => {
    logger.info('Sending password reset email for user:', req.user.email);
    sendResetPasswordEmail(res, req.user.email);
  },
});

export const requestDeletionHandler = declareHandler({
  func: async (req, res) => {
    const environment = process.env.ENVIRONMENT || 'development';

    const { message, confirmation } = req.body;
    const userId = req.user.id;
    if (confirmation !== 'delete' || userId === null) {
      return res.status(400).send({ error: 'Bad Request' });
    }

    // Add user into deletion_requests
    await query(`INSERT INTO user_deletion_requests (user_id) VALUES ($1)`, [userId]);
    await sendDiscordDeletionRequestMessage(
      `[${environment}] User \`${userId}\` (\`${req.user.email}\`) has requested account deletion.\n\n${
        message ?? 'No message'
      }`
    );
    res.send({ success: true });
  },
});
