const { NetworkAddress, Asset, CoinNetwork,Network, Wallet, WalletAddress } = require("../models");

const { Op } = require("sequelize");
const { generateRandomString } = require("./helpers");

/**
 * Production-safe wallet generator
 *
 * Business rule:
 * - One user gets one deposit address per network
 * - Multiple coins on the same network reuse that same address
 * - One Wallet row per user + coin
 * - One WalletAddress row per user + coin + network
 *
 * Recommended DB constraints:
 * 1) Wallet: UNIQUE(user_id, coin_id)
 * 2) WalletAddress: UNIQUE(user_id, coin_id, network_id)
 * 3) NetworkAddress: ideally unique per id already, plus indexed on (networkId, status)
 *
 * Important:
 * - Must run inside a transaction
 * - Relies on DB uniqueness for true race-safety
 */
const walletGenerator = async (userId, t) => {
  if (!userId) {
    throw new Error("userId is required");
  }

  if (!t) {
    throw new Error("walletGenerator must be called with an active transaction");
  }

  // 1. Fetch wallet-enabled crypto assets and their networks
  const coins = await Asset.findAll({
    where: {
      allow_wallet: true,
      type: "crypto",
      status: 'active',
    },
    attributes: ["id", "symbol"],
    include: [
      {
        model: CoinNetwork,
        as: "networks",
        attributes: ["coinId", "networkId"],
        required: false,
      },
    ],
    transaction: t,
    lock: t.LOCK.SHARE,
  });

 


  if (!coins.length) {
    return { status: true, walletsCreated: 0, walletAddressesCreated: 0 };
  }


  // 2. Ensure one Wallet per user+coin
  let walletsCreated = 0;

  for (const coin of coins) {
    const [wallet, created] = await Wallet.findOrCreate({
      where: {
        userId,
        coinId: coin.id,
      },
      defaults: {
        userId,
        coinId: coin.id,
        status: true,
      },
      transaction: t,
      lock: t.LOCK.UPDATE,
    });
    
    if (created) walletsCreated += 1;

    // Optional self-heal if old row exists with wrong/inactive status
    if (!wallet.status) {
      await wallet.update(
        { status: true },
        { transaction: t }
      );
    }
  }

  // 3. Load existing wallet-address mappings for this user
  const existingWalletAddresses = await WalletAddress.findAll({
    where: { userId },
    attributes: ["id", "coinId", "networkId", "networkAddressId", "status"],
    transaction: t,
    lock: t.LOCK.UPDATE,
  });

 
  
  // Map: networkId -> networkAddressId
  // This lets all coins on same network share one address for this user
  const userNetworkAddressMap = new Map();

  // Exact duplicate guard: user+coin+network
  const existingTripleKeys = new Set();

  for (const row of existingWalletAddresses) {
    const coinId = Number(row.coinId);
    const networkId = Number(row.networkId);
    const networkAddressId = Number(row.networkAddressId);

    existingTripleKeys.add(`${coinId}:${networkId}`);

    if (!userNetworkAddressMap.has(networkId)) {
      userNetworkAddressMap.set(networkId, {id: networkAddressId , address: row.address});
    }
  }

  // 4. Determine which networks are needed
  const needednetworkIds = [
    ...new Set(
      coins.flatMap((coin) =>
        Array.isArray(coin.networks)
          ? coin.networks.map((n) => Number(n.networkId))
          : []
      )
    ),
  ];

   

  // 5. For networks missing an assigned address, reserve one available address
  // We do this per-network with row locking for correctness.
  const newlyAssignedAddressIds = [];

  for (const networkId of needednetworkIds) {
    if (userNetworkAddressMap.has(networkId)) continue;

    // Find one available address and lock it
    const networkAddress = await NetworkAddress.findOne({
      where: {
        networkId: networkId,
        status: "available",
      },
      order: [["id", "ASC"]],
      transaction: t,
      lock: t.LOCK.UPDATE,
      skipLocked: true, // useful where supported
    });

    if (!networkAddress) {
      // No available address on this network, skip safely
      continue;
    }

    // Claim it atomically
    const [updatedCount] = await NetworkAddress.update(
      {
        assignedUserId: userId,
        status: "assigned",
        assignedAt: new Date(),
      },
      {
        where: {
          id: networkAddress.id,
          status: "available",
        },
        transaction: t,
      }
    );

    // If update failed, another transaction claimed it first
    if (updatedCount !== 1) {
      continue;
    }

    userNetworkAddressMap.set(networkId, {id: Number(networkAddress.id), address: networkAddress.address});
    newlyAssignedAddressIds.push(Number(networkAddress.id));
  }

  // 6. Ensure WalletAddress per user+coin+network using shared network address
  let walletAddressesCreated = 0;

  for (const coin of coins) {
    const coinId = Number(coin.id);
    const coinNetworks = Array.isArray(coin.networks) ? coin.networks : [];

    for (const cn of coinNetworks) {
      const networkId = Number(cn.networkId);
      const exactKey = `${coinId}:${networkId}`;

      if (existingTripleKeys.has(exactKey)) {
        continue;
      }

      const networkAddressM = userNetworkAddressMap.get(networkId);
      if (!networkAddressM) {
        continue;
      }
     networkAddressId  = networkAddressM?.id;
     networkWalletAddress = networkAddressM?.address
      const [walletAddress, created] = await WalletAddress.findOrCreate({
        where: {
          userId,
          coinId,
          networkId,
        },
        defaults: {
          userId,
          coinId,
          networkId,
          networkAddressId,
          address:networkWalletAddress,
          status: "active",
        },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      if (created) {
        walletAddressesCreated += 1;
        existingTripleKeys.add(exactKey);
      } else {
        // Optional self-heal for old/inconsistent rows
        const updates = {};

        if (!walletAddress.networkAddressId) {
          updates.networkAddressId = networkAddressId;
        }
        if (walletAddress.status !== "active") {
          updates.status = "active";
        }

        if (Object.keys(updates).length) {
          await walletAddress.update(updates, { transaction: t });
        }
      }
    }
  }

  return {
    status: true,
    walletsCreated,
    walletAddressesCreated,
    newlyAssignedAddressIds,
  };
};

const createwallet = (userId) => {
    Wallet.create({
        userId,
        coinId: 989,
        status: true
    })
}
module.exports = {
    walletGenerator,
    createwallet
}