const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const logger = require("../utils/logger");
const { parseStripeError, PaymentError } = require("../utils/stripeErrors");
const { User } = require("../models");
const emailServices = require("./email");

class StripeService {
  static async getCheckoutSession(sessionId) {
    try {
      return await stripe.checkout.sessions.retrieve(sessionId, {
        expand: ["setup_intent", "setup_intent.payment_method"],
      });
    } catch (error) {
      logger.error("Error retrieving checkout session", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async createConnectedAccount({ email, country = "US" }) {
    try {
      const account = await stripe.accounts.create({
        type: "express",
        email,
        country,
        capabilities: {
          transfers: { requested: true },
        },
      });

      return account;
    } catch (error) {
      logger.error("Error creating connected account", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async createAccountLink(accountId, refreshUrl, returnUrl) {
    try {
      const accountLink = await stripe.accountLinks.create({
        account: accountId,
        refresh_url: refreshUrl,
        return_url: returnUrl,
        type: "account_onboarding",
      });

      return accountLink;
    } catch (error) {
      logger.error("Error creating account link", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async getAccountStatus(accountId) {
    try {
      const account = await stripe.accounts.retrieve(accountId);
      return {
        id: account.id,
        details_submitted: account.details_submitted,
        payouts_enabled: account.payouts_enabled,
        capabilities: account.capabilities,
        requirements: account.requirements,
      };
    } catch (error) {
      logger.error("Error retrieving account status", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async createAccountSession(accountId) {
    try {
      const accountSession = await stripe.accountSessions.create({
        account: accountId,
        components: {
          account_onboarding: { enabled: true },
        },
      });

      return accountSession;
    } catch (error) {
      logger.error("Error creating account session", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async createTransfer({
    amount,
    currency = "usd",
    destination,
    metadata = {},
  }) {
    try {
      // Generate idempotency key from rental ID to prevent duplicate transfers
      const idempotencyKey = metadata?.rentalId
        ? `transfer_rental_${metadata.rentalId}`
        : undefined;

      const transfer = await stripe.transfers.create(
        {
          amount: Math.round(amount * 100), // Convert to cents
          currency,
          destination,
          metadata,
        },
        idempotencyKey ? { idempotencyKey } : undefined
      );

      return transfer;
    } catch (error) {
      // Check if this is a disconnected account error (fallback for missed webhooks)
      if (this.isAccountDisconnectedError(error)) {
        logger.warn("Transfer failed - account appears disconnected", {
          destination,
          errorCode: error.code,
          errorType: error.type,
        });

        // Clean up stale connection data asynchronously (don't block the error)
        this.handleDisconnectedAccount(destination).catch((cleanupError) => {
          logger.error("Failed to clean up disconnected account", {
            destination,
            error: cleanupError.message,
          });
        });
      }

      logger.error("Error creating transfer", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  /**
   * Check if error indicates the connected account is disconnected.
   * Used as fallback detection when webhook was missed.
   * @param {Error} error - Stripe error object
   * @returns {boolean} - True if error indicates disconnected account
   */
  static isAccountDisconnectedError(error) {
    // Stripe returns these error codes when account is disconnected or invalid
    const disconnectedCodes = ["account_invalid", "platform_api_key_expired"];

    // Error messages that indicate disconnection
    const disconnectedMessages = [
      "cannot transfer",
      "not connected",
      "no longer connected",
      "account has been deauthorized",
    ];

    if (disconnectedCodes.includes(error.code)) {
      return true;
    }

    const message = (error.message || "").toLowerCase();
    return disconnectedMessages.some((msg) => message.includes(msg));
  }

  /**
   * Handle disconnected account - cleanup and notify.
   * Called as fallback when webhook was missed.
   * @param {string} accountId - The disconnected Stripe account ID
   */
  static async handleDisconnectedAccount(accountId) {
    try {
      const user = await User.findOne({
        where: { stripeConnectedAccountId: accountId },
      });

      if (!user) {
        return;
      }

      logger.warn("Cleaning up disconnected account (webhook likely missed)", {
        userId: user.id,
        accountId,
      });

      // Clear connection
      await user.update({
        stripeConnectedAccountId: null,
        stripePayoutsEnabled: false,
      });

      // Send notification
      await emailServices.payment.sendAccountDisconnectedEmail(user.email, {
        ownerName: user.firstName || user.lastName,
        hasPendingPayouts: true, // We're in a transfer, so there's at least one
        pendingPayoutCount: 1,
      });

      logger.info("Sent account disconnected notification (fallback)", {
        userId: user.id,
      });
    } catch (cleanupError) {
      logger.error("Failed to clean up disconnected account", {
        accountId,
        error: cleanupError.message,
      });
      // Don't throw - let original error propagate
    }
  }

  static async createRefund({
    paymentIntentId,
    amount,
    metadata = {},
    reason = "requested_by_customer",
  }) {
    try {
      // Generate idempotency key - include amount to allow multiple partial refunds
      const idempotencyKey = metadata?.rentalId
        ? `refund_rental_${metadata.rentalId}_${Math.round(amount * 100)}`
        : undefined;

      const refund = await stripe.refunds.create(
        {
          payment_intent: paymentIntentId,
          amount: Math.round(amount * 100), // Convert to cents
          metadata,
          reason,
        },
        idempotencyKey ? { idempotencyKey } : undefined
      );

      return refund;
    } catch (error) {
      logger.error("Error creating refund", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async getRefund(refundId) {
    try {
      return await stripe.refunds.retrieve(refundId);
    } catch (error) {
      logger.error("Error retrieving refund", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async chargePaymentMethod(
    paymentMethodId,
    amount,
    customerId,
    metadata = {}
  ) {
    try {
      // Generate idempotency key to prevent duplicate charges for same rental
      const idempotencyKey = metadata?.rentalId
        ? `charge_rental_${metadata.rentalId}`
        : undefined;

      // Create a payment intent with the stored payment method
      const paymentIntent = await stripe.paymentIntents.create(
        {
          amount: Math.round(amount * 100), // Convert to cents
          currency: "usd",
          payment_method: paymentMethodId,
          customer: customerId, // Include customer ID
          confirm: true, // Automatically confirm the payment
          off_session: true, // Indicate this is an off-session payment
          return_url: `${
            process.env.FRONTEND_URL || "http://localhost:3000"
          }/complete-payment`,
          metadata,
          expand: ["latest_charge.payment_method_details"], // Expand to get payment method details
        },
        idempotencyKey ? { idempotencyKey } : undefined
      );

      // Check if additional authentication is required
      if (paymentIntent.status === "requires_action") {
        return {
          status: "requires_action",
          requiresAction: true,
          paymentIntentId: paymentIntent.id,
          clientSecret: paymentIntent.client_secret,
        };
      }

      // Extract payment method details from latest_charge
      const charge = paymentIntent.latest_charge;
      const paymentMethodDetails = charge?.payment_method_details;

      // Build payment method info object
      let paymentMethod = null;
      if (paymentMethodDetails) {
        const type = paymentMethodDetails.type;
        if (type === "card") {
          paymentMethod = {
            type: "card",
            brand: paymentMethodDetails.card?.brand || "card",
            last4: paymentMethodDetails.card?.last4 || "****",
          };
        } else if (type === "us_bank_account") {
          paymentMethod = {
            type: "bank",
            brand: "bank_account",
            last4: paymentMethodDetails.us_bank_account?.last4 || "****",
          };
        } else {
          paymentMethod = {
            type: type || "unknown",
            brand: type || "payment",
            last4: null,
          };
        }
      }

      return {
        status: "succeeded",
        paymentIntentId: paymentIntent.id,
        clientSecret: paymentIntent.client_secret,
        paymentMethod: paymentMethod,
        chargedAt: new Date(paymentIntent.created * 1000), // Convert Unix timestamp to Date
        amountCharged: amount, // Original amount in dollars
      };
    } catch (error) {
      // Handle authentication_required error (thrown for off-session 3DS)
      if (error.code === "authentication_required") {
        return {
          status: "requires_action",
          requiresAction: true,
          paymentIntentId: error.payment_intent?.id,
          clientSecret: error.payment_intent?.client_secret,
        };
      }

      // Parse Stripe error into structured format
      const parsedError = parseStripeError(error);

      logger.error("Payment failed", {
        code: parsedError.code,
        ownerMessage: parsedError.ownerMessage,
        originalError: parsedError._originalMessage,
        stripeCode: parsedError._stripeCode,
        paymentMethodId,
        customerId,
        amount,
        stack: error.stack,
      });

      throw new PaymentError(parsedError);
    }
  }

  static async createCustomer({ email, name, metadata = {} }) {
    try {
      const customer = await stripe.customers.create({
        email,
        name,
        metadata,
      });

      return customer;
    } catch (error) {
      logger.error("Error creating customer", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }

  static async getPaymentMethod(paymentMethodId) {
    try {
      return await stripe.paymentMethods.retrieve(paymentMethodId);
    } catch (error) {
      logger.error("Error retrieving payment method", {
        error: error.message,
        paymentMethodId,
      });
      throw error;
    }
  }

  static async createSetupCheckoutSession({ customerId, metadata = {} }) {
    try {
      const session = await stripe.checkout.sessions.create({
        customer: customerId,
        payment_method_types: ["card", "link"],
        mode: "setup",
        ui_mode: "embedded",
        redirect_on_completion: "never",
        // Configure for off-session usage - triggers 3DS during setup
        payment_method_options: {
          card: {
            request_three_d_secure: "any",
          },
        },
        metadata: {
          type: "payment_method_setup",
          ...metadata,
        },
      });

      return session;
    } catch (error) {
      logger.error("Error creating setup checkout session", {
        error: error.message,
        stack: error.stack,
      });
      throw error;
    }
  }
}

module.exports = StripeService;
