"use strict";

const { Network, NetworkAddress, CoinNetwork, Coin, Asset, User } = require("../../models");
const { Op } = require("sequelize");
const { ValidationError, NotFoundError, ConflictError } = require("../../utils/errors");

class adminNetworkService {
  // -----------------------------
  // Networks
  // -----------------------------
  async listNetworks(query = {}) {
    const { q, status, allowDeposit, allowWithdrawal, page = 1, limit = 20 } = query;
    const offset = (Number(page) - 1) * Number(limit);

    const where = {};
    if (typeof status !== "undefined" && status !== "") where.status = String(status) === "true";
    if (typeof allowDeposit !== "undefined" && allowDeposit !== "") where.allowDeposit = String(allowDeposit) === "true";
    if (typeof allowWithdrawal !== "undefined" && allowWithdrawal !== "") where.allowWithdrawal = String(allowWithdrawal) === "true";

    if (q) {
      where[Op.or] = [
        { network: { [Op.like]: `%${q}%` } },
        { name: { [Op.like]: `%${q}%` } },
      ];
    }

    const { rows, count } = await Network.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit: Number(limit),
      offset,
    });

    return { count, page: Number(page), limit: Number(limit), networks: rows };
  }

  async getNetworkById(id) {
    const network = await Network.findByPk(id);
    if (!network) throw new NotFoundError("Network not found");
    return { network };
  }

  async createNetwork(data) {
    const network = String(data?.network || "").trim();
    if (!network) throw new ValidationError("network is required");

    const exists = await Network.findOne({ where: { network } });
    if (exists) throw new ConflictError("Network already exists");

    return await Network.create({
      network,
      name: data?.name ?? null,
      usedAddress: data?.usedAddress ?? null,
      allowDeposit: typeof data?.allowDeposit === "boolean" ? data.allowDeposit : true,
      allowWithdrawal: typeof data?.allowWithdrawal === "boolean" ? data.allowWithdrawal : true,
      minWithdrawal: data?.minWithdrawal ?? 0,
      withdrawalFee: data?.withdrawalFee ?? 0,
      confirmation: data?.confirmation ?? null,
      status: typeof data?.status === "boolean" ? data.status : true,
    });
  }

  async updateNetwork(id, data) {
    const network = await Network.findByPk(id);
    if (!network) throw new NotFoundError("Network not found");

    if (data?.network && String(data.network).trim() !== String(network.network)) {
      const n = String(data.network).trim();
      const exists = await Network.findOne({ where: { network: n, id: { [Op.ne]: id } } });
      if (exists) throw new ConflictError("Network already exists");
    }

    await network.update({
      network: data?.network ?? network.network,
      name: typeof data?.name !== "undefined" ? data.name : network.name,
      usedAddress: typeof data?.usedAddress !== "undefined" ? data.usedAddress : network.usedAddress,
      allowDeposit: typeof data?.allowDeposit === "boolean" ? data.allowDeposit : network.allowDeposit,
      allowWithdrawal: typeof data?.allowWithdrawal === "boolean" ? data.allowWithdrawal : network.allowWithdrawal,
      minWithdrawal: typeof data?.minWithdrawal !== "undefined" ? data.minWithdrawal : network.minWithdrawal,
      withdrawalFee: typeof data?.withdrawalFee !== "undefined" ? data.withdrawalFee : network.withdrawalFee,
      confirmation: typeof data?.confirmation !== "undefined" ? data.confirmation : network.confirmation,
      status: typeof data?.status === "boolean" ? data.status : network.status,
    });

    return network;
  }

  async deleteNetwork(id) {
    const network = await Network.findByPk(id);
    if (!network) throw new NotFoundError("Network not found");

    const inUseAddr = await NetworkAddress.count({ where: { networkId: id } });
    const inUseCoin = await CoinNetwork.count({ where: { networkId: id } });

    if (inUseAddr > 0 || inUseCoin > 0) {
      throw new ConflictError("Cannot delete: network is referenced by addresses or coin networks");
    }

    await network.destroy(); // paranoid true -> soft delete
    return true;
  }

  // -----------------------------
  // Network Addresses
  // -----------------------------
  async listNetworkAddresses(query = {}) {
    const { q, status, networkId, assignedUserId, page = 1, limit = 20 } = query;
    const offset = (Number(page) - 1) * Number(limit);

    const where = {};
    if (status) where.status = status; // 'available' | 'assigned' | 'retired'
    if (networkId) where.networkId = networkId;
    if (assignedUserId) where.assignedUserId = assignedUserId;

    if (q) {
      where[Op.or] = [{ address: { [Op.like]: `%${q}%` } }];
    }

    const { rows, count } = await NetworkAddress.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit: Number(limit),
      offset,
      include: [{ model: Network, as: "network", required: false }],
    });
    const users = await User.findAll({
      attributes: ['id' , 'email']
    })

    return { count, page: Number(page), limit: Number(limit), addresses: rows, users };
  }

  async getNetworkAddressById(id) {
    const address = await NetworkAddress.findByPk(id, {
      include: [{ model: Network, as: "network", required: false }],
    });
    if (!address) throw new NotFoundError("Network address not found");
    return { address };
  }

  async createNetworkAddress(data) {
    const networkId = Number(data?.networkId);
    const address = String(data?.address || "").trim();
    const status = data?.status || "available";

    if (!Number.isFinite(networkId) || networkId <= 0) throw new ValidationError("networkId is required");
    if (!address) throw new ValidationError("address is required");
    if (!["available", "assigned", "retired"].includes(status)) throw new ValidationError("Invalid status");

    const network = await Network.findByPk(networkId);
    if (!network) throw new NotFoundError("Network not found");

    const exists = await NetworkAddress.findOne({ where: { networkId, address } });
    if (exists) throw new ConflictError("Address already exists for this network");

    const assignedUserId = data?.assignedUserId ? Number(data.assignedUserId) : null;

    return await NetworkAddress.create({
      networkId,
      address,
      status,
      assignedUserId,
      assignedAt: assignedUserId ? new Date() : null,
    });
  }

  async updateNetworkAddress(id, data) {
    const row = await NetworkAddress.findByPk(id);
    if (!row) throw new NotFoundError("Network address not found");

    if (data?.networkId && Number(data.networkId) !== Number(row.networkId)) {
      const net = await Network.findByPk(data.networkId);
      if (!net) throw new NotFoundError("Network not found");
    }

    if (data?.address && String(data.address).trim() !== String(row.address)) {
      const exists = await NetworkAddress.findOne({
        where: {
          networkId: data?.networkId ?? row.networkId,
          address: String(data.address).trim(),
          id: { [Op.ne]: id },
        },
      });
      if (exists) throw new ConflictError("Address already exists for this network");
    }

    const nextAssigned = typeof data?.assignedUserId !== "undefined"
      ? (data.assignedUserId ? Number(data.assignedUserId) : null)
      : row.assignedUserId;

    const nextStatus = data?.status ?? row.status;
    if (!["available", "assigned", "retired"].includes(nextStatus)) throw new ValidationError("Invalid status");

    await row.update({
      networkId: data?.networkId ?? row.networkId,
      address: typeof data?.address !== "undefined" ? String(data.address).trim() : row.address,
      status: nextStatus,
      assignedUserId: nextAssigned,
      assignedAt: nextAssigned ? (row.assignedAt ?? new Date()) : null,
    });

    return row;
  }

  async deleteNetworkAddress(id) {
    const row = await NetworkAddress.findByPk(id);
    if (!row) throw new NotFoundError("Network address not found");
    await row.destroy();
    return true;
  }

  // -----------------------------
  // Coin Networks
  // -----------------------------
  async listCoinNetworks(query = {}) {
    const { coinId, networkId, status, page = 1, limit = 20 } = query;
    const offset = (Number(page) - 1) * Number(limit);

    const where = {};
    if (coinId) where.coinId = coinId;
    if (networkId) where.networkId = networkId;
    if (typeof status !== "undefined" && status !== "") where.status = String(status) === "true";

    const { rows, count } = await CoinNetwork.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit: Number(limit),
      offset,
      include: [
        { model: Network, as: "network", required: true },
        { model: Asset, as: "coin", required: true },
      ],
    });

    return { count, page: Number(page), limit: Number(limit), coinNetworks: rows };
  }

  async getCoinNetworkById(id) {
    const coinNetwork = await CoinNetwork.findByPk(id, {
      include: [
        { model: Network, as: "network", required: true },
        { model: Asset, as: "coin", required: true },
      ],
    });
    if (!coinNetwork) throw new NotFoundError("Coin network not found");
    return { coinNetwork };
  }

  async createCoinNetwork(data) {
    const coinId = Number(data?.coinId);
    const networkId = Number(data?.networkId);

    if (!Number.isFinite(coinId) || coinId <= 0) throw new ValidationError("coinId is required");
    if (!Number.isFinite(networkId) || networkId <= 0) throw new ValidationError("networkId is required");

    const network = await Network.findByPk(networkId);
    if (!network) throw new NotFoundError("Network not found");

    const coin = await Asset.findByPk(coinId);
    if (!coin) throw new NotFoundError("Coin not found");

    const exists = await CoinNetwork.findOne({ where: { coinId, networkId } });
    if (exists) throw new ConflictError("This coin/network pair already exists");

    return await CoinNetwork.create({
      coinId,
      networkId,
      withdrawalFee: typeof data?.withdrawalFee !== "undefined" ? data.withdrawalFee : 0,
      status: typeof data?.status === "boolean" ? data.status : true,
    });
  }

  async updateCoinNetwork(id, data) {
    const row = await CoinNetwork.findByPk(id);
    if (!row) throw new NotFoundError("Coin network not found");

    const nextCoinId = typeof data?.coinId !== "undefined" ? Number(data.coinId) : Number(row.coinId);
    const nextNetworkId = typeof data?.networkId !== "undefined" ? Number(data.networkId) : Number(row.networkId);

    if (Number.isFinite(nextCoinId) && Number.isFinite(nextNetworkId)) {
      const exists = await CoinNetwork.findOne({
        where: {
          coinId: nextCoinId,
          networkId: nextNetworkId,
          id: { [Op.ne]: id },
        },
      });
      if (exists) throw new ConflictError("This coin/network pair already exists");
    }

    await row.update({
      coinId: nextCoinId,
      networkId: nextNetworkId,
      withdrawalFee: typeof data?.withdrawalFee !== "undefined" ? data.withdrawalFee : row.withdrawalFee,
      status: typeof data?.status === "boolean" ? data.status : row.status,
    });

    return row;
  }

  async deleteCoinNetwork(id) {
    const row = await CoinNetwork.findByPk(id);
    if (!row) throw new NotFoundError("Coin network not found");
    await row.destroy(); // paranoid true -> soft delete
    return true;
  }
}

module.exports = new adminNetworkService();