import { Router, Request, Response } from 'express';
import fs from 'fs';
import path from 'path';
import { getNetworks, getConfigByChain, API_VERSION } from '../../config/networks';
import { cacheMiddleware } from '../middleware/cache';
import { fetchRemoteJson } from '../utils/fetch-remote-json';
import { logger } from '../../utils/logger';

const router: Router = Router();

type RuntimeNetworksPayload = {
  version?: string | { major?: number; minor?: number; patch?: number };
  networks?: unknown[];
  chains?: unknown[];
};

type NetworksPayload = {
  source: 'remote-url' | 'runtime-file' | 'built-in';
  version: string;
  networks: unknown[];
  lastModified?: string;
};

function uniquePaths(paths: Array<string | undefined | null>): string[] {
  const seen = new Set<string>();
  const out: string[] = [];

  for (const candidate of paths) {
    if (typeof candidate !== 'string') continue;
    const trimmed = candidate.trim();
    if (!trimmed || seen.has(trimmed)) continue;
    seen.add(trimmed);
    out.push(trimmed);
  }

  return out;
}

function resolveRuntimeNetworksPath(): string | null {
  const candidates = uniquePaths([
    process.env.NETWORKS_JSON_PATH,
    process.env.CONFIG_NETWORKS_JSON_PATH,
    path.resolve(process.cwd(), 'explorer-monorepo/backend/api/rest/config/metamask/DUAL_CHAIN_NETWORKS.json'),
    path.resolve(process.cwd(), '../explorer-monorepo/backend/api/rest/config/metamask/DUAL_CHAIN_NETWORKS.json'),
    path.resolve(process.cwd(), '../../explorer-monorepo/backend/api/rest/config/metamask/DUAL_CHAIN_NETWORKS.json'),
    path.resolve(process.cwd(), 'explorer-monorepo/backend/config/metamask/DUAL_CHAIN_NETWORKS.json'),
    path.resolve(process.cwd(), '../explorer-monorepo/backend/config/metamask/DUAL_CHAIN_NETWORKS.json'),
    path.resolve(process.cwd(), '../../explorer-monorepo/backend/config/metamask/DUAL_CHAIN_NETWORKS.json'),
    path.resolve(__dirname, '../../../../../../explorer-monorepo/backend/api/rest/config/metamask/DUAL_CHAIN_NETWORKS.json'),
  ]);

  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) return candidate;
  }
  return null;
}

function normalizeVersion(value: RuntimeNetworksPayload['version']): string {
  if (typeof value === 'string' && value.trim() !== '') return value.trim();
  if (value && typeof value === 'object') {
    const major = 'major' in value ? Number(value.major ?? 0) : 0;
    const minor = 'minor' in value ? Number(value.minor ?? 0) : 0;
    const patch = 'patch' in value ? Number(value.patch ?? 0) : 0;
    return `${major}.${minor}.${patch}`;
  }
  return API_VERSION;
}

function extractNetworks(payload: RuntimeNetworksPayload | { version?: string; networks?: unknown[] }): unknown[] {
  if (Array.isArray(payload.networks)) return payload.networks;
  if ('chains' in payload && Array.isArray(payload.chains)) return payload.chains;
  return [];
}

async function loadRemoteNetworksPayload(): Promise<NetworksPayload | null> {
  const networksJsonUrl = process.env.NETWORKS_JSON_URL?.trim();
  if (!networksJsonUrl) return null;

  try {
    const data = (await fetchRemoteJson(networksJsonUrl)) as RuntimeNetworksPayload;
    return {
      source: 'remote-url',
      version: normalizeVersion(data.version),
      networks: extractNetworks(data),
    };
  } catch (error) {
    logger.error('NETWORKS_JSON_URL fetch failed, trying runtime file/built-in networks:', error);
    return null;
  }
}

function loadRuntimeNetworksPayload(): NetworksPayload | null {
  const filePath = resolveRuntimeNetworksPath();
  if (!filePath) return null;

  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    const stat = fs.statSync(filePath);
    const parsed = JSON.parse(raw) as RuntimeNetworksPayload;

    return {
      source: 'runtime-file',
      version: normalizeVersion(parsed.version),
      networks: extractNetworks(parsed),
      lastModified: stat.mtime.toISOString(),
    };
  } catch (error) {
    logger.error('NETWORKS_JSON_PATH read failed, using built-in networks:', error);
    return null;
  }
}

async function resolveNetworksPayload(): Promise<NetworksPayload> {
  const remotePayload = await loadRemoteNetworksPayload();
  if (remotePayload) return remotePayload;

  const runtimePayload = loadRuntimeNetworksPayload();
  if (runtimePayload) return runtimePayload;

  return {
    source: 'built-in',
    version: API_VERSION,
    networks: getNetworks(),
  };
}

/**
 * GET /api/v1/networks
 * Full EIP-3085 chain params for wallet_addEthereumChain (Chain 138, 1, 651940).
 * If NETWORKS_JSON_URL is set (e.g. GitHub raw URL), fetches and returns that JSON; otherwise uses built-in networks.
 */
router.get('/networks', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
  try {
    res.set('Cache-Control', 'public, max-age=0, must-revalidate');
    const payload = await resolveNetworksPayload();
    res.json(payload);
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

/**
 * GET /api/v1/config
 * Oracles (and optional config) per chain. Query: chainId (optional).
 * If chainId provided, returns config for that chain only.
 */
router.get('/config', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
  try {
    res.set('Cache-Control', 'public, max-age=0, must-revalidate');
    const chainIdParam = req.query.chainId as string | undefined;
    const payload = await resolveNetworksPayload();
    const networks = payload.networks as Array<{ chainIdDecimal?: number; oracles?: unknown[] }>;
    if (chainIdParam) {
      const chainId = parseInt(chainIdParam, 10);
      const matchingNetwork = networks.find((network) => Number(network.chainIdDecimal) === chainId);
      const config = matchingNetwork
        ? {
            oracles: (matchingNetwork.oracles as unknown[]) ?? [],
          }
        : getConfigByChain(chainId);
      if (!config) {
        return res.status(404).json({ error: 'Chain not found', chainId });
      }
      return res.json({
        source: payload.source,
        version: payload.version,
        chainId,
        ...config,
      });
    }
    const chains = networks.map((n) => ({
      chainId: Number(n.chainIdDecimal),
      oracles: Array.isArray(n.oracles) ? n.oracles : [],
    }));
    res.json({
      source: payload.source,
      version: payload.version,
      chains,
    });
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

export default router;
