const { User, UserBalance, UserPlan, Plan, Transaction, sequelize } = require("../../models");
const { Op } = require("sequelize");
const { ValidationError, NotFoundError, BadRequestError, ConflictError } = require("../../utils/errors");
const { generateTransactionId } = require("../../utils/helpers");
const userService = require("./userService");

class adminUserPlanService {
  async listUserPlans(userId, query = {}) {
    const page = Math.max(parseInt(query.page || "1", 10), 1);
    const limit = Math.min(Math.max(parseInt(query.limit || "50", 10), 1), 200);
    const offset = (page - 1) * limit;

    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const where = { userId };
    if (query.status) where.status = query.status;

    const { rows, count } = await UserPlan.findAndCountAll({
      where,
      limit,
      offset,
      order: [["created_at", "DESC"]],
      include: [{ model: Plan, as: "plan" }],
    });

    return {
      count,
      page,
      limit,
      userPlans: rows,
    };
  }

  async listPlans(query = {}) {
    const page = Math.max(parseInt(query.page || "1", 10), 1);
    const limit = Math.min(Math.max(parseInt(query.limit || "50", 10), 1), 200);
    const offset = (page - 1) * limit;

    const where = {};
    if (query.status) where.status = query.status;

    const { rows, count } = await UserPlan.findAndCountAll({
      where,
      limit,
      offset,
      order: [["created_at", "DESC"]],
      include: [{ model: Plan, as: "plan" }],
    });

    return {
      count,
      page,
      limit,
      userPlans: rows,
    };
  }

  async createUserPlan(userId, data, adminId) {
    const { planId, amount, coupon = null, debitFromBalance = true } = data;

    const amountNum = Number(amount);
    if (!Number.isFinite(amountNum) || amountNum <= 0) {
      throw new ValidationError("Invalid amount");
    }

    return await sequelize.transaction(async (t) => {
      const user = await User.findByPk(userId, { transaction: t, lock: t.LOCK.UPDATE });
      if (!user) throw new NotFoundError("User not found");

      const plan = await Plan.findByPk(planId, { transaction: t });
      if (!plan) throw new NotFoundError("Plan not found");

      // Lock balance only if we debit
      let balanceRow = null;
      let currentBalance = null;

      if (debitFromBalance) {
        balanceRow = await UserBalance.findOne({
          where: { userId },
          transaction: t,
          lock: t.LOCK.UPDATE,
        });
        if (!balanceRow) throw new ConflictError("User balance not found");

        currentBalance = Number(balanceRow.balance ?? 0);
        if (amountNum > currentBalance) throw new ValidationError("Insufficient balance");
      }

      // create investment transaction
      const tx = await Transaction.create(
        {
          transactionId: generateTransactionId("INV"),
          userId,
          source: "funding",
          kind: "investment",
          direction: "debit",
          toType: "internal",
          currency: user.currency,
          amount: amountNum,
          status: "completed",
          description: `Admin purchase ${plan.name} plan`,
          planId: plan.id,
          metadata: {
            adminId: adminId || null,
            coupon,
            planId: plan.id,
            planName: plan.name,
            debitFromBalance: !!debitFromBalance,
          },
        },
        { transaction: t }
      );

      const createdPlan = await UserPlan.create(
        {
          planId: plan.id,
          userId,
          reference: tx.transactionId,
          amount: amountNum,
          status: "active",
          roi: Number(plan.roi ?? 0),
          investment_duration: {
            duration: plan.duration,
            period: plan.durationType,
          },
          metadata: {
            roi_type: plan.roiType,
            roi_interval: plan.roiInterval,
            adminId: adminId || null,
            coupon,
          },
          activated_at: new Date(),
        },
        { transaction: t }
      );

      if (debitFromBalance) {
        await balanceRow.update(
          { balance: currentBalance - amountNum },
          { transaction: t }
        );
      }

      return { user, createdPlan, transaction: tx };
    });
  }

  async updateUserPlan(userId, userPlanId, data, adminId) {
    const allowed = [
      "status",
      "endDate",
      "activatedAt",
      "autoReinvest",
      "metadata",
      "exchangeRate",
      "investmentDuration",
      "roi",
      "totalReturn",
      "lastPayoutAt",
    ];

    const patch = {};
    for (const k of allowed) {
      if (data[k] !== undefined) patch[k] = data[k];
    }

    // map camelCase -> model fields (your model uses field mappings)
    if (patch.endDate !== undefined) patch.endDate = patch.endDate ? new Date(patch.endDate) : null;
    if (patch.activatedAt !== undefined) patch.activatedAt = patch.activatedAt ? new Date(patch.activatedAt) : null;
    if (patch.lastPayoutAt !== undefined) patch.lastPayoutAt = patch.lastPayoutAt ? new Date(patch.lastPayoutAt) : null;

    if (patch.status && !["pending", "active", "completed", "cancelled", "paused", "ended"].includes(String(patch.status))) {
      throw new ValidationError("Invalid status");
    }

    return await sequelize.transaction(async (t) => {
      const user = await User.findByPk(userId, { transaction: t });
      if (!user) throw new NotFoundError("User not found");

      const userPlan = await UserPlan.findOne({
        where: { id: userPlanId, userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
        include: [{ model: Plan, as: "plan" }],
      });
      if (!userPlan) throw new NotFoundError("UserPlan not found");

      // append admin action log
      const meta = userPlan.metadata || {};
      const logs = Array.isArray(meta.adminActions) ? meta.adminActions : [];
      logs.push({
        action: "update",
        adminId: adminId || null,
        at: new Date().toISOString(),
        patch,
      });
      meta.adminActions = logs;
      patch.metadata = patch.metadata !== undefined ? patch.metadata : meta;

      await userPlan.update(patch, { transaction: t });
      return { userPlan };
    });
  }

  async pauseUserPlan(userId, userPlanId, data = {}, adminId) {
    const reason = data?.reason ?? null;

    return await sequelize.transaction(async (t) => {
      const userPlan = await UserPlan.findOne({
        where: { id: userPlanId, userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!userPlan) throw new NotFoundError("UserPlan not found");

      if (userPlan.status !== "active") {
        throw new BadRequestError("Only active plans can be paused");
      }

      const meta = userPlan.metadata || {};
      const logs = Array.isArray(meta.adminActions) ? meta.adminActions : [];
      logs.push({
        action: "pause",
        reason,
        adminId: adminId || null,
        at: new Date().toISOString(),
      });
      meta.adminActions = logs;

      await userPlan.update(
        { status: "paused", metadata: meta },
        { transaction: t }
      );

      userService.notifyUser(userPlan.userId, {
        title: "Plan Paused",
        message: `Your plan ${userPlan.plan?.name || ""} has been paused. Reason: ${reason || "No reason provided"}`,
        type: "investment",
        channels: { inApp: true, push: true },
        priority: "medium",
      }, adminId).catch(err => console.error("Plan pause notification failed:", err.message));

      return { userPlan };
    });
  }

  async resumeUserPlan(userId, userPlanId, data = {}, adminId) {
    const note = data?.note ?? null;

    return await sequelize.transaction(async (t) => {
      const userPlan = await UserPlan.findOne({
        where: { id: userPlanId, userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!userPlan) throw new NotFoundError("UserPlan not found");

      if (userPlan.status !== "paused") {
        throw new BadRequestError("Only paused plans can be resumed");
      }

      const meta = userPlan.metadata || {};
      const logs = Array.isArray(meta.adminActions) ? meta.adminActions : [];
      logs.push({
        action: "resume",
        note,
        adminId: adminId || null,
        at: new Date().toISOString(),
      });
      meta.adminActions = logs;

      await userPlan.update(
        { status: "active", metadata: meta },
        { transaction: t }
      );

      userService.notifyUser(userPlan.userId, {
        title: "Plan Resumed",
        message: `Your plan ${userPlan.plan?.name || ""} with reference ${userPlan.reference} has been resumed. Note: ${note || "No additional note"}`,
        type: "investment",
        channels: { inApp: true, push: true },
        priority: "medium",
      }, adminId).catch(err => console.error("Plan resume notification failed:", err.message));

      return { userPlan };
    });
  }

  async stopUserPlan(userId, userPlanId, data = {}, adminId) {
    const reason = data?.reason ?? null;

    return await sequelize.transaction(async (t) => {
      const userPlan = await UserPlan.findOne({
        where: { id: userPlanId, userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!userPlan) throw new NotFoundError("UserPlan not found");

      if (!["active", "paused", "pending"].includes(userPlan.status)) {
        throw new BadRequestError("Only pending/active/paused plans can be stopped");
      }

      const meta = userPlan.metadata || {};
      const logs = Array.isArray(meta.adminActions) ? meta.adminActions : [];
      logs.push({
        action: "stop",
        reason,
        adminId: adminId || null,
        at: new Date().toISOString(),
      });
      meta.adminActions = logs;

      await userPlan.update(
        {
          status: "ended",
          endDate: new Date(),
          metadata: meta,
        },
        { transaction: t }
      );

    userService.notifyUser(userPlan.userId, {
        title: "Plan Stopped",
        message: `Your plan ${userPlan.plan?.name || ""} with reference ${userPlan.reference} has been stopped. Reason: ${reason || "No reason provided"}`,
        type: "investment",
        channels: { inApp: true, push: true },
        priority: "medium",
      }, adminId).catch(err => console.error("Plan stop notification failed:", err.message));

      return { userPlan };
    });
  }

  async addRoiToUserPlan(userId, userPlanId, data, adminId) {
    const { amount } = data;

    const amountNum = Number(amount);
    if (!Number.isFinite(amountNum) || amountNum <= 0) {
      throw new ValidationError("Invalid amount");
    }

    return await sequelize.transaction(async (t) => {
      const user = await User.findByPk(userId, { transaction: t, lock: t.LOCK.UPDATE });
      if (!user) throw new NotFoundError("User not found");

      const userPlan = await UserPlan.findOne({
        where: { id: userPlanId, userId },
        include: [{ model: Plan, as: "plan" }],
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!userPlan) throw new NotFoundError("UserPlan not found");

      if (!["active", "paused", "completed"].includes(userPlan.status)) {
        throw new BadRequestError("ROI can only be added to active/paused/completed plans");
      }

      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!balance) throw new ConflictError("User balance not found");

      const currentBalance = Number(balance.balance ?? 0);
      const currentTotalReturn = Number(userPlan.totalReturn ?? 0);

      // create ROI transaction
      const tx = await Transaction.create(
        {
          transactionId: generateTransactionId("ROI"),
          userId,
          source: "funding",
          kind: "roi",
          direction: "credit",
          toType: "internal",
          currency: user.currency,
          amount: amountNum,
          status: "completed",
          description: `ROI on ${userPlan.plan?.name || "plan"} (ref: ${userPlan.reference})`,
          planId: userPlan.planId,
          metadata: {
            adminId: adminId || null,
            userPlanId: userPlan.id,
            reference: userPlan.reference,
          },
        },
        { transaction: t }
      );

      // update plan totals
      const meta = userPlan.metadata || {};
      const logs = Array.isArray(meta.adminActions) ? meta.adminActions : [];
      logs.push({
        action: "add_roi",
        amount: amountNum,
        adminId: adminId || null,
        at: new Date().toISOString(),
        txId: tx.transactionId,
      });
      meta.adminActions = logs;

      await userPlan.update(
        {
          totalReturn: currentTotalReturn + amountNum,
          lastPayoutAt: new Date(),
          metadata: meta,
        },
        { transaction: t }
      );

      // credit wallet balance
      await balance.update(
        { balance: currentBalance + amountNum },
        { transaction: t }
      );

      /* await userService.notifyUser(userPlan.userId, {
        title: "ROI Added to Plan",
        message: `Your plan ${userPlan.plan?.name || ""} with reference ${userPlan.reference} has received a new ROI. Amount: ${amountNum}`,
        type: "investment",
        channels: { inApp: true, push: true },
        priority: "medium",
      }, adminId); */
      userService.notifyUser(userId, {
        title: "ROI Credited to Your Plan 💰",
        message: `Your investment plan "${userPlan.plan?.name || "your plan"}" (Ref: ${userPlan.reference}) has received an ROI payout of $${amountNum.toLocaleString()}. The amount has been added to your wallet balance.`,
        type: "investment",
        channels: { inApp: true, push: true },
        priority: "medium",
      }, adminId).catch(err => console.error("ROI notification failed:", err.message));

      return { userPlan, transaction: tx, balance };
    });
  }
}

module.exports = new adminUserPlanService();