const Agenda = require('agenda');
const { TwoFactorAuthMailOption } = require('../email/email');
const { sendMail } = require('../services/email');
const env = require('../config/env');
const axios = require('axios');
const Account = require('../models/account.model');
const Wallet = require('../models/wallet.model');
const mongoose = require('mongoose');

// MongoDB connection string for Agenda
const mongoConnectionString = env.MONGODB_URI;

// Initialize Agenda with MongoDB
const agenda = new Agenda({
  db: {
    address: mongoConnectionString,
    collection: 'agendaJobs',
    options: {
      useUnifiedTopology: true,
    },
  },
  processEvery: '1 minute',
  maxConcurrency: 20,
  defaultConcurrency: 5,
  defaultLockLifetime: 10000, // 10 seconds
});

// Define email job processor
agenda.define('SEND_EMAIL', async (job) => {
  const { to, type, data } = job.attrs.data;

  try {
    let mailOptions = { to };

    switch (type) {
      case 'WELCOME':
        mailOptions = {
          ...mailOptions,
          subject: 'Welcome to Our Platform',
          html: `<h1>Welcome!</h1><p>Thank you for joining our platform.</p>`,
        };
        break;

      case 'PASSWORD_RESET':
        mailOptions = {
          ...mailOptions,
          subject: 'Password Reset Request',
          html: `
            <h1>Password Reset</h1>
            <p>Click <a href="${data.resetLink}">here</a> to reset your password.</p>
            <p>This link will expire in 1 hour.</p>
          `,
        };
        break;

      case '2FA':
        mailOptions = {
          ...TwoFactorAuthMailOption({ to, code: data.code }),
          to,
        };
        break;

      default:
        throw new Error(`Unknown email type: ${type}`);
    }

    await sendMail(mailOptions);
    console.log(`Email sent to ${to} (Type: ${type})`);
  } catch (error) {
    console.error(`Failed to send email to ${to} (Type: ${type}):`, error);
    // Optionally implement retry logic here
    throw error; // This will make Agenda retry the job
  }
});

// Define transfer recipient creation job
agenda.define('CREATE_TRANSFER_RECIPIENT', async (job) => {
  const { userId, accountNumber, bankCode } = job.attrs.data;

  if (!mongoose.Types.ObjectId.isValid(userId)) {
    throw new Error(`Invalid userId format: ${userId}`);
  }

  const account = await Account.findById(userId);
  if (!account) {
    throw new Error(`Account not found for userId: ${userId}`);
  }

  try {
    const response = await axios.post(
      `https://api.paystack.co/transferrecipient`,
      {
        type: 'nuban',
        name: `${account.firstName} ${account.lastName}`,
        account_number: accountNumber,
        bank_code: bankCode,
        currency: 'NGN',
        metadata: { userId: userId.toString() },
      },
      {
        headers: {
          Authorization: `Bearer ${env.PAYSTACK_SECRET_KEY}`,
          'Content-Type': 'application/json',
        },
      },
    );

    if (response.data.status) {
      await Wallet.updateOne(
        { user: userId },
        {
          transferRecipientId: response.data.data.id,
          transferRecipientCode: response.data.data.recipient_code,
        },
      );
      console.log(`Transfer recipient created for userId: ${userId}`);
    } else {
      throw new Error(
        `Failed to create transfer recipient: ${response.data.message}`,
      );
    }
  } catch (error) {
    console.error(
      `Error creating transfer recipient for userId ${userId}:`,
      error,
    );
    throw error; // This will make Agenda retry the job
  }
});

// Define notification cleanup job
agenda.define('CLEANUP_OLD_NOTIFICATIONS', async (job) => {
  const { days = 90 } = job.attrs.data || {};
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - days);

  try {
    const Notification = require('../models/Notification');
    const result = await Notification.deleteMany({
      createdAt: { $lt: cutoffDate },
      isRead: true,
    });

    console.log(`Cleaned up ${result.deletedCount} old notifications`);
  } catch (error) {
    console.error('Error cleaning up notifications:', error);
    throw error;
  }
});

// Define order payment release job
agenda.define('RELEASE_PAYMENT_TO_SELLER', async (job) => {
  const { orderId } = job.attrs.data;

  try {
    const Order = require('../models/Order');
    const OrderProduct = require('../models/OrderProduct');
    const Transaction = require('../models/Transaction');
    const Wallet = require('../models/Wallet');

    const session = await mongoose.startSession();
    session.startTransaction();

    try {
      const order = await Order.findById(orderId).session(session);
      if (!order || order.status !== 'completed') {
        await session.abortTransaction();
        session.endSession();
        return;
      }

      // Get order products to find sellers
      const orderProducts = await OrderProduct.find({ order: orderId })
        .populate('product')
        .session(session);

      // Group by seller and calculate amounts
      const sellerAmounts = {};
      for (const op of orderProducts) {
        const sellerId = op.product.userId.toString();
        if (!sellerAmounts[sellerId]) {
          sellerAmounts[sellerId] = 0;
        }
        sellerAmounts[sellerId] += op.product.price * op.quantity;
      }

      // Release payments to sellers
      for (const [sellerId, amount] of Object.entries(sellerAmounts)) {
        const sellerWallet = await Wallet.findOne({ user: sellerId }).session(
          session,
        );
        if (sellerWallet) {
          sellerWallet.balance += amount;
          await sellerWallet.save({ session });

          await Transaction.create(
            [
              {
                amount,
                reference: `RELEASE-${Date.now()}-${sellerId}`,
                type: 'credit',
                method: 'wallet',
                purpose: 'purchase',
                sender: order.user,
                receiver: sellerId,
                status: 'success',
                description: `Payment released for order #${orderId}`,
              },
            ],
            { session },
          );
        }
      }

      await session.commitTransaction();
      session.endSession();
      console.log(`Payment released for order ${orderId}`);
    } catch (error) {
      await session.abortTransaction();
      session.endSession();
      throw error;
    }
  } catch (error) {
    console.error(`Error releasing payment for order ${orderId}:`, error);
    throw error;
  }
});

// Error handling for the queue
agenda.on('fail', (error, job) => {
  console.error(
    `Job ${job.attrs.name} failed for ${job.attrs.data?.to || job.attrs.data?.userId}:`,
    error,
  );
  // You might want to implement additional failure handling here
});

agenda.on('start', (job) => {
  console.log(`Job ${job.attrs.name} starting`);
});

agenda.on('complete', (job) => {
  console.log(`Job ${job.attrs.name} completed`);
});

// Graceful shutdown
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

function gracefulShutdown() {
  agenda.stop(() => {
    console.log('Agenda stopped gracefully');
    process.exit(0);
  });
}

// Start the agenda instance when your app starts
async function startAgenda() {
  try {
    // Wait for MongoDB connection to be ready
    await agenda.start();
    console.log('Agenda email queue started');

    // Schedule recurring jobs
    await agenda.every('24 hours', 'CLEANUP_OLD_NOTIFICATIONS', { days: 90 });
    console.log('Scheduled recurring jobs');
  } catch (error) {
    console.error('Failed to start Agenda:', error);
    throw error;
  }
}

// Utility functions for scheduling jobs
async function scheduleEmail(to, type, data, scheduleTime = 'now') {
  try {
    if (scheduleTime === 'now') {
      await agenda.now('SEND_EMAIL', { to, type, data });
    } else {
      await agenda.schedule(scheduleTime, 'SEND_EMAIL', { to, type, data });
    }
    console.log(`Scheduled ${type} email to ${to} for ${scheduleTime}`);
  } catch (error) {
    console.error(`Failed to schedule email to ${to}:`, error);
    throw error;
  }
}

async function scheduleTransferRecipientCreation(
  userId,
  accountNumber,
  bankCode,
  scheduleTime = 'now',
) {
  try {
    if (scheduleTime === 'now') {
      await agenda.now('CREATE_TRANSFER_RECIPIENT', {
        userId,
        accountNumber,
        bankCode,
      });
    } else {
      await agenda.schedule(scheduleTime, 'CREATE_TRANSFER_RECIPIENT', {
        userId,
        accountNumber,
        bankCode,
      });
    }
    console.log(`Scheduled transfer recipient creation for user ${userId}`);
  } catch (error) {
    console.error(`Failed to schedule transfer recipient creation:`, error);
    throw error;
  }
}

async function schedulePaymentRelease(orderId, delayMs = 24 * 60 * 60 * 1000) {
  try {
    const scheduleTime = new Date(Date.now() + delayMs);
    await agenda.schedule(scheduleTime, 'RELEASE_PAYMENT_TO_SELLER', {
      orderId,
    });
    console.log(
      `Scheduled payment release for order ${orderId} at ${scheduleTime}`,
    );
  } catch (error) {
    console.error(`Failed to schedule payment release:`, error);
    throw error;
  }
}

module.exports = {
  agenda,
  startAgenda,
  scheduleEmail,
  scheduleTransferRecipientCreation,
  schedulePaymentRelease,
};
