import { Router, Request, Response } from "express";
import { WebhookRequestError } from "../services/AlchemyWebhookPayloadValidator";
import { DepositWorkerService } from "../services/DepositWorkerService";
import { AlchemyMultiChainService } from "../services/AlchemyMultiChainService";
import { MultiChainSweeperService } from "../services/MultiChainSweeperService";
import { IDepositQueueStore } from "../services/StateStoreContracts";
import { WalletRegistrationService } from "../services/WalletRegistrationService";
import { IWebhookProcessorPort } from "../providers/ProviderPorts";

function parseWallets(input: unknown) {
  return Array.isArray(input) ? input : [];
}

function toErrorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

function resolveDerivationIdentityKey(input: Record<string, unknown>): number {
  const candidate = Number(
    input?.derivation_index_key
      ?? input?.derivationIndexKey
      ?? input?.derivation_index
      ?? input?.derivationIndex
      ?? input?.userId
      ?? input?.user_id
  );

  return Number.isInteger(candidate) && candidate > 0 ? candidate : 0;
}

export function createOrchestratorRoutes(
  walletRegistrationService: WalletRegistrationService,
  alchemyMultiChainService: AlchemyMultiChainService,
  multiChainSweeperService: MultiChainSweeperService,
  webhookProcessor: IWebhookProcessorPort,
  depositQueueService: IDepositQueueStore,
  depositWorkerService: DepositWorkerService,
): Router {
  const router = Router();

  const registerWalletsHandler = async (request: Request, response: Response) => {
    try {
      const derivationIdentityKey = resolveDerivationIdentityKey(request.body || {});
      const wallets = parseWallets(request.body?.wallets);

      if (!derivationIdentityKey || wallets.length === 0) {
        return response.status(400).json({
          status: 400,
          error: "derivation_index_key (or legacy userId) and wallets are required",
        });
      }

      const data = await walletRegistrationService.registerWallets(derivationIdentityKey, wallets);
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  };

  router.post("/wallets/register", registerWalletsHandler);
  router.post("/create-wallets", registerWalletsHandler);

  router.post("/chains/:chain/wallets/register", async (request: Request, response: Response) => {
    try {
      const derivationIdentityKey = resolveDerivationIdentityKey(request.body || {});
      const address = String(request.body?.address || "").trim();
      const walletId = typeof request.body?.wallet_id === "string" ? request.body.wallet_id : undefined;
      const chain = String(request.params.chain || "");

      if (!derivationIdentityKey || !address || !chain) {
        return response.status(400).json({
          status: 400,
          error: "chain, derivation_index_key (or legacy userId) and address are required",
        });
      }

      const data = await alchemyMultiChainService.registerWallet(derivationIdentityKey, chain, address, walletId);
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.post("/chains/:chain/wallets/generate", async (request: Request, response: Response) => {
    try {
      const derivationIdentityKey = resolveDerivationIdentityKey(request.body || {});
      const chain = String(request.params.chain || "");
      const walletId = typeof request.body?.wallet_id === "string" ? request.body.wallet_id : undefined;

      if (!derivationIdentityKey || !chain) {
        return response.status(400).json({
          status: 400,
          error: "chain and derivation_index_key (or legacy userId) are required",
        });
      }

      const data = await alchemyMultiChainService.generateWalletForDerivationKey(derivationIdentityKey, chain, walletId, {
        organization_id: typeof request.body?.organization_id === "string" ? request.body.organization_id : null,
        branch_id: typeof request.body?.branch_id === "string" ? request.body.branch_id : null,
        terminal_id: typeof request.body?.terminal_id === "string" ? request.body.terminal_id : null,
        payment_id: typeof request.body?.payment_id === "string" ? request.body.payment_id : null,
        supported_asset_id: typeof request.body?.supported_asset_id === "string" ? request.body.supported_asset_id : null,
        network: typeof request.body?.network === "string" ? request.body.network : null,
      });
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.post("/tracking/addresses/remove", async (request: Request, response: Response) => {
    try {
      const addresses = Array.isArray(request.body?.addresses)
        ? request.body.addresses.filter((address: unknown): address is string => typeof address === "string" && address.trim().length > 0)
        : [];

      if (addresses.length === 0) {
        return response.status(400).json({
          status: 400,
          error: "addresses are required",
        });
      }

      const data = await walletRegistrationService.removeTrackedAddresses(addresses);
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.get("/chains/:chain/transactions", async (request: Request, response: Response) => {
    try {
      const chain = String(request.params.chain || "");
      const address = String(request.query.address || "").trim();
      const direction = String(request.query.direction || "all").toLowerCase();
      const limit = Number(request.query.limit || 25);
      const contractAddress = typeof request.query.contract_address === "string"
        ? request.query.contract_address
        : undefined;

      if (!chain || !address) {
        return response.status(400).json({
          status: 400,
          error: "chain and address are required",
        });
      }

      if (!["incoming", "outgoing", "all"].includes(direction)) {
        return response.status(400).json({
          status: 400,
          error: "direction must be one of incoming, outgoing, all",
        });
      }

      const data = await alchemyMultiChainService.fetchLatestTransactions(
        chain,
        address,
        Number.isFinite(limit) ? limit : 25,
        direction as "incoming" | "outgoing" | "all",
        contractAddress
      );

      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.get("/chains/:chain/transactions/:txId", async (request: Request, response: Response) => {
    try {
      const chain = String(request.params.chain || "");
      const txId = String(request.params.txId || "");

      if (!chain || !txId) {
        return response.status(400).json({
          status: 400,
          error: "chain and txId are required",
        });
      }

      const data = await alchemyMultiChainService.getTransaction(chain, txId);
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.get("/chains/:chain/addresses/:address/balance", async (request: Request, response: Response) => {
    try {
      const chain = String(request.params.chain || "");
      const address = String(request.params.address || "").trim();
      const contractAddress = typeof request.query.contract_address === "string"
        ? request.query.contract_address
        : undefined;

      if (!chain || !address) {
        return response.status(400).json({
          status: 400,
          error: "chain and address are required",
        });
      }

      const data = await alchemyMultiChainService.getAddressBalance(chain, address, contractAddress);
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.get("/chains/:chain/balances/total", async (request: Request, response: Response) => {
    try {
      const chain = String(request.params.chain || "");
      const contractAddress = typeof request.query.contract_address === "string"
        ? request.query.contract_address
        : undefined;
      console.log("Received total balance request", { chain, contractAddress });
      if (!chain) {
        return response.status(400).json({
          status: 400,
          error: "chain is required",
        });
      }

      const data = await alchemyMultiChainService.getTotalTrackedBalance(chain, contractAddress);
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.post("/chains/:chain/transactions/withdraw", async (request: Request, response: Response) => {
    try {
      const chain = String(request.params.chain || "");
      const data = await multiChainSweeperService.withdraw(chain, request.body || {});
      return response.status(200).json({ status: 200, data });
    } catch (error: any) {
      const status = error?.statusCode || 500;
      console.error("withdraw route failed", {
        chain: request.params.chain,
        body: request.body,
        status,
        message: error?.message,
        stack: error?.stack,
      });
      return response.status(status).json({ status, error: error.message });
    }
  });

  router.post("/webhook", async (request: Request, response: Response) => {
    console.log("Received webhook request before processing", {
      headers: request.headers,
      body: request.body,
    });
    try {
      console.log("Received webhook request", {
        headers: request.headers,
        body: request.body,
      });
      const rawBody = (request as Request & { rawBody?: string }).rawBody;
      const { events, metrics } = await webhookProcessor.process(request.body, request.headers, rawBody);
      console.log("Webhook processed with metrics", metrics);
      const queueResult = await depositQueueService.enqueue(events);

      void depositWorkerService.processBatch().catch((error: unknown) => {
        console.error("deposit worker batch failed", toErrorMessage(error));
      });

      return response.status(200).json({
        status: 200,
        data: {
          accepted: queueResult.accepted,
          duplicates: queueResult.duplicates,
          ignored: metrics.ignored,
          unsupported: metrics.unsupported,
          reorg_events: metrics.reorgEvents,
        },
      });
    } catch (error: any) {
      console.error("Webhook route failed", {
        message: error?.message,
        name: error?.name,
        stack: error?.stack,
      });
      const statusCode = error instanceof WebhookRequestError ? error.statusCode : 500;
      return response.status(statusCode).json({ status: statusCode, error: error.message });
    }
  });

  router.post("/relay/flush", async (_request: Request, response: Response) => {
    try {
      const result = await depositWorkerService.processBatch();
      return response.status(200).json({ status: 200, data: result });
    } catch (error: any) {
      return response.status(500).json({ status: 500, error: error.message });
    }
  });

  return router;
}
