const VERBOSE = process.env.MIKROTIK_DEBUG === '1' || process.env.MIKROTIK_DEBUG === 'true';

interface RouterConfig {
  id: string;
  ipAddress: string;
  apiPort: number;
  username: string;
  password: string;
  enabled: boolean;
}

interface BlockResult {
  routerIp: string;
  routerName?: string;
  success: boolean;
  alreadyExists?: boolean;
  error?: string;
}

async function mikrotikRequest(
  router: RouterConfig,
  method: string,
  path: string,
  body?: any,
  timeoutMs: number = 8000
): Promise<{ status: number; data: any }> {
  const useHttps = router.apiPort === 443;
  const protocol = useHttps ? "https" : "http";
  const url = `${protocol}://${router.ipAddress}:${router.apiPort}${path}`;
  const auth = Buffer.from(`${router.username}:${router.password}`).toString("base64");
  const startTime = Date.now();
  
  const origTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
  if (useHttps) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const fetchOptions: RequestInit = {
      method,
      headers: {
        "Authorization": `Basic ${auth}`,
        "Content-Type": "application/json",
      },
      signal: controller.signal,
    };

    if (body) {
      fetchOptions.body = JSON.stringify(body);
    }

    const response = await fetch(url, fetchOptions);
    clearTimeout(timeout);

    let data: any;
    const text = await response.text();
    try {
      data = JSON.parse(text);
    } catch {
      data = text;
    }

    const elapsed = Date.now() - startTime;
    if (VERBOSE) {
      const bodyStr = body ? ` body=${JSON.stringify(body)}` : '';
      const dataPreview = typeof data === 'string' ? data.substring(0, 200) : JSON.stringify(data).substring(0, 200);
      console.log(`[MIKROTIK] ${method} ${url} => HTTP ${response.status} (${elapsed}ms)${bodyStr} response=${dataPreview}`);
    } else if (response.status >= 400) {
      const dataPreview = typeof data === 'string' ? data.substring(0, 100) : JSON.stringify(data).substring(0, 100);
      console.warn(`[MIKROTIK] ${method} ${router.ipAddress}${path} => HTTP ${response.status} (${elapsed}ms) err=${dataPreview}`);
    } else if (elapsed > 5000) {
      console.warn(`[MIKROTIK] SLOW: ${method} ${router.ipAddress}${path} => HTTP ${response.status} (${elapsed}ms)`);
    }

    return { status: response.status, data };
  } catch (error: any) {
    clearTimeout(timeout);
    const elapsed = Date.now() - startTime;
    const errMsg = error.name === 'AbortError' ? `TIMEOUT after ${timeoutMs}ms` : error.message;
    console.error(`[MIKROTIK] ${method} ${url} => ERRORE: ${errMsg} (${elapsed}ms)`);
    if (useHttps && origTlsReject !== undefined) {
      process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject;
    } else if (useHttps) {
      delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
    }
    throw error;
  } finally {
    if (useHttps) {
      if (origTlsReject !== undefined) {
        process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject;
      } else {
        delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
      }
    }
  }
}

export async function testRouterConnection(router: RouterConfig): Promise<boolean> {
  try {
    const { status } = await mikrotikRequest(router, "GET", "/rest/system/identity");
    return status === 200;
  } catch {
    return false;
  }
}

export async function getExistingBlockedIps(
  router: RouterConfig,
  listName: string = "ddos_blocked"
): Promise<Set<string>> {
  try {
    if (VERBOSE) console.log(`[MIKROTIK] Fetching address-list da router ${router.ipAddress} (list=${listName}, timeout=20s)...`);
    const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list", undefined, 20000);
    if (status === 200 && Array.isArray(data)) {
      const ips = new Set<string>();
      const allLists = new Map<string, number>();
      for (const entry of data) {
        const count = allLists.get(entry.list) || 0;
        allLists.set(entry.list, count + 1);
        if (entry.list === listName) {
          ips.add(entry.address);
        }
      }
      const listsInfo = Array.from(allLists.entries()).map(([name, count]) => `${name}:${count}`).join(', ');
      console.log(`[MIKROTIK] Router ${router.ipAddress}: ${data.length} entries totali (${listsInfo}), ${ips.size} in list "${listName}"`);
      return ips;
    }
    console.warn(`[MIKROTIK] Router ${router.ipAddress}: risposta inattesa status=${status}, data non e' array`);
    return new Set();
  } catch (e: any) {
    console.error(`[MIKROTIK] Router ${router.ipAddress}: ERRORE fetch address-list: ${e.message}`);
    return new Set();
  }
}

export async function addToAddressList(
  router: RouterConfig,
  ipAddress: string,
  listName: string = "ddos_blocked",
  comment: string = "",
  timeoutDuration: string = "1h"
): Promise<BlockResult> {
  try {
    const { status, data } = await mikrotikRequest(router, "POST", "/rest/ip/firewall/address-list/add", {
      list: listName,
      address: ipAddress,
      comment: comment || `IDS block ${new Date().toISOString()}`,
      timeout: timeoutDuration,
    });

    if (status === 200 || status === 201) {
      if (VERBOSE) console.log(`[BLOCK] OK: ${ipAddress} aggiunto su router ${router.ipAddress} (HTTP ${status})`);
      return { routerIp: router.ipAddress, success: true };
    }

    if (status === 400 || status === 409) {
      const text = typeof data === "string" ? data.toLowerCase() : JSON.stringify(data).toLowerCase();
      if (text.includes("already") || text.includes("exists") || text.includes("duplicate") || text.includes("failure: already")) {
        if (VERBOSE) console.log(`[BLOCK] SKIP: ${ipAddress} gia' presente su router ${router.ipAddress} (HTTP ${status})`);
        return { routerIp: router.ipAddress, success: true, alreadyExists: true };
      }
      console.warn(`[BLOCK] VERIFICA: ${ipAddress} su router ${router.ipAddress} HTTP ${status} risposta="${text.substring(0, 150)}", verifico lista...`);
      try {
        const verifyResult = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
        if (verifyResult.status === 200 && Array.isArray(verifyResult.data)) {
          for (const entry of verifyResult.data) {
            if (entry.address === ipAddress && entry.list === listName) {
              console.log(`[BLOCK] CONFERMATO: ${ipAddress} trovato nella lista di router ${router.ipAddress} dopo verifica`);
              return { routerIp: router.ipAddress, success: true, alreadyExists: true };
            }
          }
        }
      } catch (verifyErr: any) {
        console.error(`[BLOCK] ERRORE verifica: ${ipAddress} su router ${router.ipAddress}: ${verifyErr.message}`);
      }
      const errMsg = `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`;
      console.error(`[BLOCK] FALLITO: ${ipAddress} su router ${router.ipAddress}: ${errMsg}`);
      return {
        routerIp: router.ipAddress,
        success: false,
        error: errMsg,
      };
    }

    const errMsg = `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`;
    console.error(`[BLOCK] FALLITO: ${ipAddress} su router ${router.ipAddress}: ${errMsg}`);
    return {
      routerIp: router.ipAddress,
      success: false,
      error: errMsg,
    };
  } catch (error: any) {
    const errMsg = error.name === 'AbortError' ? `TIMEOUT (8s)` : (error.message || "Connection failed");
    console.error(`[BLOCK] ERRORE: ${ipAddress} su router ${router.ipAddress}: ${errMsg}`);
    return {
      routerIp: router.ipAddress,
      success: false,
      error: errMsg,
    };
  }
}

export async function removeFromAddressList(
  router: RouterConfig,
  ipAddress: string,
  listName: string = "ddos_blocked"
): Promise<BlockResult> {
  try {
    if (VERBOSE) console.log(`[UNBLOCK] Rimozione ${ipAddress} da router ${router.ipAddress} (list=${listName})...`);
    const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
    if (status !== 200 || !Array.isArray(data)) {
      console.error(`[UNBLOCK] ERRORE: impossibile leggere address-list da router ${router.ipAddress}: HTTP ${status}`);
      return { routerIp: router.ipAddress, success: false, error: "Failed to read address list" };
    }

    for (const entry of data) {
      if (entry.address === ipAddress && entry.list === listName) {
        const entryId = entry[".id"];
        const delResult = await mikrotikRequest(router, "DELETE", `/rest/ip/firewall/address-list/${entryId}`);
        if (delResult.status === 200 || delResult.status === 204) {
          console.log(`[UNBLOCK] OK: ${ipAddress} rimosso da router ${router.ipAddress}`);
          return { routerIp: router.ipAddress, success: true };
        }
        console.error(`[UNBLOCK] FALLITO: eliminazione ${ipAddress} da router ${router.ipAddress}: HTTP ${delResult.status}`);
        return { routerIp: router.ipAddress, success: false, error: `Delete failed: ${delResult.status}` };
      }
    }

    if (VERBOSE) console.log(`[UNBLOCK] ${ipAddress} non trovato su router ${router.ipAddress} (gia' assente)`);
    return { routerIp: router.ipAddress, success: true };
  } catch (error: any) {
    console.error(`[UNBLOCK] ERRORE: ${ipAddress} su router ${router.ipAddress}: ${error.message}`);
    return { routerIp: router.ipAddress, success: false, error: error.message };
  }
}

export async function blockIpOnAllRouters(
  routers: RouterConfig[],
  ipAddress: string,
  listName: string = "ddos_blocked",
  comment: string = "",
  timeoutDuration: string = "1h"
): Promise<BlockResult[]> {
  const enabled = routers.filter((r) => r.enabled);
  const results = await Promise.allSettled(
    enabled.map((r) => addToAddressList(r, ipAddress, listName, comment, timeoutDuration))
  );

  return results.map((r, i) =>
    r.status === "fulfilled" ? r.value : { routerIp: enabled[i].ipAddress, success: false, error: String(r.reason) }
  );
}

export async function unblockIpOnAllRouters(
  routers: RouterConfig[],
  ipAddress: string,
  listName: string = "ddos_blocked"
): Promise<BlockResult[]> {
  const enabled = routers.filter((r) => r.enabled);
  const results = await Promise.allSettled(
    enabled.map((r) => removeFromAddressList(r, ipAddress, listName))
  );

  return results.map((r, i) =>
    r.status === "fulfilled" ? r.value : { routerIp: enabled[i].ipAddress, success: false, error: String(r.reason) }
  );
}

export async function bulkBlockIps(
  routers: RouterConfig[],
  ipList: string[],
  listName: string = "ddos_blocked",
  commentPrefix: string = "IDS bulk-block",
  timeoutDuration: string = "1h",
  concurrency: number = 10
): Promise<{ blocked: number; failed: number; skipped: number; details: Array<{ ip: string; status: string }> }> {
  const enabled = routers.filter((r) => r.enabled);
  if (enabled.length === 0) {
    return { blocked: 0, failed: 0, skipped: 0, details: [] };
  }

  console.log(`[BULK-BLOCK] Starting: ${ipList.length} IPs on ${enabled.length} routers (${enabled.map(r => r.ipAddress).join(', ')})`);

  const routerStatus = new Map<string, { ok: number; fail: number; skip: number }>();
  for (const r of enabled) {
    routerStatus.set(r.ipAddress, { ok: 0, fail: 0, skip: 0 });
  }

  const existingCache = new Map<string, Set<string>>();
  await Promise.allSettled(
    enabled.map(async (router) => {
      const start = Date.now();
      const existing = await getExistingBlockedIps(router, listName);
      const elapsed = Date.now() - start;
      existingCache.set(router.ipAddress, existing);
      console.log(`[BULK-BLOCK] Router ${router.ipAddress}: ${existing.size} IPs already in list (${elapsed}ms)`);
    })
  );

  const newIps: string[] = [];
  const skippedIps: string[] = [];

  for (const ip of ipList) {
    let alreadyOnAll = true;
    for (const router of enabled) {
      const existing = existingCache.get(router.ipAddress) || new Set();
      if (!existing.has(ip)) {
        alreadyOnAll = false;
        break;
      }
    }
    if (alreadyOnAll) {
      skippedIps.push(ip);
    } else {
      newIps.push(ip);
    }
  }

  console.log(`[BULK-BLOCK] ${skippedIps.length} already blocked, ${newIps.length} new to block`);

  let blocked = 0;
  let failed = 0;
  const details: Array<{ ip: string; status: string }> = [];
  const partialIps: string[] = [];
  const failedIps: string[] = [];

  async function processIp(ip: string) {
    const routerResults = await Promise.allSettled(
      enabled.map(async (router) => {
        const existing = existingCache.get(router.ipAddress) || new Set();
        if (existing.has(ip)) {
          const st = routerStatus.get(router.ipAddress);
          if (st) st.skip++;
          return { success: true, skipped: true, routerIp: router.ipAddress };
        }
        const start = Date.now();
        const result = await addToAddressList(router, ip, listName, `${commentPrefix} ${ip}`, timeoutDuration);
        const elapsed = Date.now() - start;
        const st = routerStatus.get(router.ipAddress);
        if (result.success) {
          if (st) st.ok++;
        } else {
          if (st) st.fail++;
        }
        return { success: result.success, skipped: false, routerIp: router.ipAddress, elapsed, error: result.error };
      })
    );

    const perRouterDetail = routerResults.map((r) => {
      if (r.status === 'fulfilled') {
        const v = r.value;
        if (v.skipped) return `${v.routerIp}:SKIP`;
        if (v.success) return `${v.routerIp}:OK(${v.elapsed}ms)`;
        return `${v.routerIp}:FAIL(${v.elapsed}ms,${v.error})`;
      }
      return 'REJECTED';
    }).join(' | ');

    const anySuccess = routerResults.some(
      (r) => r.status === "fulfilled" && r.value.success
    );
    const allSuccess = routerResults.every(
      (r) => r.status === "fulfilled" && r.value.success
    );

    if (anySuccess) {
      blocked++;
      details.push({ ip, status: "blocked" });
      if (!allSuccess) {
        partialIps.push(ip);
        if (VERBOSE) console.warn(`[BULK-BLOCK] PARZIALE: IP ${ip}: ${perRouterDetail}`);
      }
    } else {
      failed++;
      failedIps.push(ip);
      details.push({ ip, status: "failed" });
      if (VERBOSE) console.error(`[BULK-BLOCK] FALLITO: IP ${ip}: ${perRouterDetail}`);
    }
  }

  const bulkStart = Date.now();
  for (let i = 0; i < newIps.length; i += concurrency) {
    const batch = newIps.slice(i, i + concurrency);
    await Promise.allSettled(batch.map((ip) => processIp(ip)));

    const progress = Math.min(i + concurrency, newIps.length);
    if (progress === newIps.length || progress % 50 === 0) {
      const elapsed = ((Date.now() - bulkStart) / 1000).toFixed(1);
      console.log(`[BULK-BLOCK] Progress: ${progress}/${newIps.length} (${elapsed}s, ${blocked} ok, ${failed} fail)`);
    }
  }

  for (const ip of skippedIps) {
    details.push({ ip, status: "already_blocked" });
  }

  const totalElapsed = ((Date.now() - bulkStart) / 1000).toFixed(1);
  routerStatus.forEach((st, routerIp) => {
    console.log(`[BULK-BLOCK] Router ${routerIp}: ${st.ok} blocked, ${st.fail} failed, ${st.skip} skipped`);
  });
  console.log(`[BULK-BLOCK] Completato in ${totalElapsed}s: ${blocked} blocked, ${failed} failed, ${skippedIps.length} already_blocked, ${partialIps.length} parziali`);
  if (failedIps.length > 0) {
    console.error(`[BULK-BLOCK] IP non bloccati su nessun router (${failedIps.length}): ${failedIps.slice(0, 20).join(', ')}${failedIps.length > 20 ? '...' : ''}`);
  }
  if (partialIps.length > 0) {
    console.warn(`[BULK-BLOCK] IP bloccati solo parzialmente (${partialIps.length}): ${partialIps.slice(0, 20).join(', ')}${partialIps.length > 20 ? '...' : ''}`);
  }

  return { blocked, failed, skipped: skippedIps.length, details };
}
