import { NextRequest } from 'next/server';
import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory';
import { ZabbixClient } from '@/lib/services/zabbix-client';
import { postgresClient } from '@/lib/services/postgres-client';
import { DattoRMMDevice } from '@/lib/types/datto-rmm';
import {
  lookupIsp,
  clearIspCache,
  buildHostParams,
  discoverIcmpTemplate,
} from '@/lib/services/zabbix-wan-utils';

export const maxDuration = 300;

type SyncMode = 'all' | 'client' | 'site';
type SiteAction = 'created' | 'updated' | 'filtered' | 'no-ip' | 'error' | 'skipped';

interface WanResolution {
  ip: string | null;
  count: number;
  multiWan: boolean;      // true when qualifying devices report 2+ distinct IPs
  allIps: string[];       // all distinct IPs seen (for multi-WAN visibility)
  singleDeviceFallback: boolean; // true when result came from the single-device fallback
  noIpReason?: string;    // set only when ip is null
}

interface SiteResult {
  siteName: string;
  siteUid: string;
  companyId: number | null;
  companyName: string | null;
  wanIp: string | null;
  qualifyingDevices: number;
  multiWan: boolean;
  singleDeviceFallback: boolean;
  isp: string | null;
  asn: string | null;
  action: SiteAction;
  hostId: string | null;
  filterReason?: string;
  error?: string;
}

interface StreamMessage {
  type: 'site' | 'summary' | 'error';
  result?: SiteResult;
  stats?: {
    total: number;
    created: number;
    updated: number;
    filtered: number;
    noIp: number;
    errors: number;
    skipped: number;
    multiWan: number;
  };
  message?: string;
}

// ---------------------------------------------------------------------------
// WAN IP resolution
// ---------------------------------------------------------------------------

function isLaptop(d: DattoRMMDevice): boolean {
  const cat  = (d.deviceType?.category ?? '').toLowerCase();
  const type = (d.deviceType?.type     ?? '').toLowerCase();
  return cat.includes('laptop') || cat.includes('notebook') ||
         type.includes('laptop') || type.includes('notebook');
}

function resolveWanIp(
  devices: DattoRMMDevice[],
  minDevices: number,
  maxLastSeenHours: number,
  allowSingleDevice: boolean,
): WanResolution {
  const cutoffMs = Date.now() - maxLastSeenHours * 3600 * 1000;

  const qualifying = devices.filter(
    (d) =>
      !d.suspended &&
      !d.deleted &&
      d.extIpAddress &&
      d.extIpAddress !== '0.0.0.0' &&
      d.extIpAddress.trim() !== '' &&
      d.lastSeen != null &&
      d.lastSeen > cutoffMs
  );

  // Group devices by IP
  const ipDevices = new Map<string, DattoRMMDevice[]>();
  for (const d of qualifying) {
    const ip = d.extIpAddress;
    if (!ipDevices.has(ip)) ipDevices.set(ip, []);
    ipDevices.get(ip)!.push(d);
  }

  // Drop IPs seen only once from a single laptop — likely a remote/travelling device
  const filtered = new Map(ipDevices);
  for (const [ip, devs] of filtered) {
    if (devs.length === 1 && isLaptop(devs[0])) {
      filtered.delete(ip);
    }
  }

  // If filtering wiped everything out and allowSingleDevice is on, fall back to
  // the full set (accepts any device type including a lone laptop/server/etc.)
  const effective = filtered.size > 0
    ? filtered
    : allowSingleDevice && ipDevices.size > 0
      ? ipDevices
      : null;

  if (!effective) {
    let noIpReason = 'No qualifying devices';
    if (devices.length === 0) {
      noIpReason = 'No devices in site';
    } else if (qualifying.length === 0) {
      const active = devices.filter((d) => !d.suspended && !d.deleted);
      if (active.length === 0) {
        noIpReason = `All ${devices.length} devices suspended or deleted`;
      } else {
        const withIp = active.filter(
          (d) => d.extIpAddress && d.extIpAddress !== '0.0.0.0' && d.extIpAddress.trim() !== ''
        );
        if (withIp.length === 0) {
          noIpReason = `${active.length} active device${active.length !== 1 ? 's' : ''}, none report a public IP`;
        } else {
          noIpReason = `${withIp.length} device${withIp.length !== 1 ? 's' : ''} have an IP but none seen in last ${maxLastSeenHours}h`;
        }
      }
    } else {
      // qualifying > 0 but all unique-laptop IPs were dropped and fallback is off
      noIpReason = 'Only laptops found — enable single-device fallback';
    }
    return { ip: null, count: 0, multiWan: false, allIps: [], singleDeviceFallback: false, noIpReason };
  }

  const sorted = Array.from(effective.entries())
    .map(([ip, devs]) => [ip, devs.length] as [string, number])
    .sort((a, b) => b[1] - a[1]);

  const allIps = sorted.map(([ip]) => ip);
  const [topIp, topCount] = sorted[0];
  const multiWan = sorted.length > 1;
  const singleDeviceFallback = filtered.size === 0; // used the fallback path

  return { ip: topIp, count: topCount, multiWan, allIps, singleDeviceFallback };
}

// ISP lookup, hostname sanitization, and host param building imported from
// @/lib/services/zabbix-wan-utils

// ---------------------------------------------------------------------------
// API route
// ---------------------------------------------------------------------------

export async function POST(request: NextRequest) {
  const body = await request.json();
  const {
    mode = 'all' as SyncMode,
    companyId,
    siteUid,
    minDevices = 1,
    maxLastSeenHours = 48,
    allowSingleDevice = false,
    dryRun = false,
  } = body;

  clearIspCache(); // fresh cache per request

  const encoder = new TextEncoder();
  const transform = new TransformStream<Uint8Array, Uint8Array>();
  const writer = transform.writable.getWriter();

  const send = async (msg: StreamMessage) => {
    await writer.write(encoder.encode(JSON.stringify(msg) + '\n'));
  };

  (async () => {
    try {
      if (!process.env.ZABBIX_API_URL || !process.env.ZABBIX_API_TOKEN) {
        await send({ type: 'error', message: 'Zabbix is not configured. Add ZABBIX_API_URL and ZABBIX_API_TOKEN to your environment.' });
        return;
      }

      const rmmClient = getDattoRMMClient();
      const zabbix = new ZabbixClient({
        apiUrl: process.env.ZABBIX_API_URL!,
        apiToken: process.env.ZABBIX_API_TOKEN!,
      });

      // Ensure base group + ICMP template (skipped in dry-run)
      let globalGroupId = 'dry-run';
      let icmpTemplateId: string | null = null;

      if (!dryRun) {
        globalGroupId = await zabbix.ensureHostGroup('Datto RMM Sites');
        icmpTemplateId = await discoverIcmpTemplate(zabbix);
      }

      // Load site → Autotask mappings (keyed by RMM site UID)
      const mappingRows = await postgresClient.query<{
        rmm_site_uid: string;
        company_id: number;
        company_name: string;
      }>(
        `SELECT rsm.rmm_site_uid, rsm.company_id, c.company_name
         FROM rmm_site_mappings rsm
         JOIN companies c ON c.id = rsm.company_id`
      );
      const mappingBySiteUid = new Map(
        mappingRows.rows.map((r) => [r.rmm_site_uid, { companyId: r.company_id, companyName: r.company_name }])
      );

      // Determine which sites to process
      let sites: { uid: string; name: string }[] = [];

      if (mode === 'all') {
        const allSites = await rmmClient.getAllSites();
        sites = allSites
          .filter((s) => mappingBySiteUid.has(s.uid))
          .map((s) => ({ uid: s.uid, name: s.name }));
      } else if (mode === 'client' && companyId) {
        const res = await postgresClient.query<{ rmm_site_uid: string; rmm_site_name: string }>(
          'SELECT rmm_site_uid, rmm_site_name FROM rmm_site_mappings WHERE company_id = $1',
          [companyId]
        );
        sites = res.rows.map((r) => ({ uid: r.rmm_site_uid, name: r.rmm_site_name }));
      } else if (mode === 'site' && siteUid) {
        const res = await postgresClient.query<{ rmm_site_name: string }>(
          'SELECT rmm_site_name FROM rmm_site_mappings WHERE rmm_site_uid = $1 LIMIT 1',
          [siteUid]
        );
        sites = [{ uid: siteUid, name: res.rows[0]?.rmm_site_name ?? siteUid }];
      }

      if (sites.length === 0) {
        await send({ type: 'error', message: 'No sites found to process.' });
        return;
      }

      const stats = { total: sites.length, created: 0, updated: 0, filtered: 0, noIp: 0, errors: 0, skipped: 0, multiWan: 0 };

      for (const site of sites) {
        const mapping = mappingBySiteUid.get(site.uid);
        let devices: DattoRMMDevice[] = [];

        try {
          devices = await rmmClient.getDevicesBySite(site.uid);
        } catch (err) {
          stats.errors++;
          await send({ type: 'site', result: {
            siteName: site.name, siteUid: site.uid,
            companyId: mapping?.companyId ?? null, companyName: mapping?.companyName ?? null,
            wanIp: null, qualifyingDevices: 0, multiWan: false, singleDeviceFallback: false, isp: null, asn: null,
            action: 'error', hostId: null, error: String(err),
          }});
          continue;
        }

        const { ip: wanIp, count: qualifyingDevices, multiWan, allIps, singleDeviceFallback, noIpReason } = resolveWanIp(devices, minDevices, maxLastSeenHours, allowSingleDevice);
        if (multiWan) stats.multiWan++;

        if (!wanIp) {
          stats.noIp++;
          await send({ type: 'site', result: {
            siteName: site.name, siteUid: site.uid,
            companyId: mapping?.companyId ?? null, companyName: mapping?.companyName ?? null,
            wanIp: null, qualifyingDevices: 0, multiWan, singleDeviceFallback: false, isp: null, asn: null,
            action: 'no-ip', hostId: null, filterReason: noIpReason,
          }});
          continue;
        }

        // ISP lookup (runs even in dry-run and for filtered sites so we can show it in preview)
        const ispInfo = await lookupIsp(wanIp);

        // Skip minDevices gate if the single-device fallback is active — the user
        // explicitly opted in, so enforcing the threshold here would silently undo it.
        if (!singleDeviceFallback && qualifyingDevices < minDevices) {
          stats.filtered++;
          await send({ type: 'site', result: {
            siteName: site.name, siteUid: site.uid,
            companyId: mapping?.companyId ?? null, companyName: mapping?.companyName ?? null,
            wanIp, qualifyingDevices, multiWan, singleDeviceFallback: false,
            isp: ispInfo?.isp ?? null, asn: ispInfo?.asn ?? null,
            action: 'filtered', hostId: null,
            filterReason: `${qualifyingDevices} device${qualifyingDevices !== 1 ? 's' : ''} at IP (min ${minDevices})`,
          }});
          continue;
        }

        if (dryRun) {
          stats.skipped++;
          await send({ type: 'site', result: {
            siteName: site.name, siteUid: site.uid,
            companyId: mapping?.companyId ?? null, companyName: mapping?.companyName ?? null,
            wanIp, qualifyingDevices, multiWan, singleDeviceFallback,
            isp: ispInfo?.isp ?? null, asn: ispInfo?.asn ?? null,
            action: 'skipped', hostId: null,
          }});
          continue;
        }

        try {
          const onlineCount = devices.filter((d) => d.online && !d.suspended && !d.deleted).length;

          const hostParams = await buildHostParams({
            siteName: site.name,
            wanIp,
            companyId: mapping?.companyId,
            companyName: mapping?.companyName,
            rmmSiteUid: site.uid,
            ispInfo,
            multiWan,
            allIps,
            singleDeviceFallback,
            onlineDeviceCount: onlineCount,
            source: 'datto-rmm',
            icmpTemplateId,
            globalGroupId,
            zabbix,
          });

          const { action, hostid } = await zabbix.upsertHost(hostParams);

          if (action === 'created') stats.created++; else stats.updated++;

          await send({ type: 'site', result: {
            siteName: site.name, siteUid: site.uid,
            companyId: mapping?.companyId ?? null, companyName: mapping?.companyName ?? null,
            wanIp, qualifyingDevices, multiWan, singleDeviceFallback,
            isp: ispInfo?.isp ?? null, asn: ispInfo?.asn ?? null,
            action, hostId: hostid,
          }});
        } catch (err) {
          stats.errors++;
          await send({ type: 'site', result: {
            siteName: site.name, siteUid: site.uid,
            companyId: mapping?.companyId ?? null, companyName: mapping?.companyName ?? null,
            wanIp, qualifyingDevices, multiWan, singleDeviceFallback,
            isp: ispInfo?.isp ?? null, asn: ispInfo?.asn ?? null,
            action: 'error', hostId: null, error: String(err),
          }});
        }
      }

      await send({ type: 'summary', stats });
    } catch (err) {
      await send({ type: 'error', message: String(err) });
    } finally {
      await writer.close();
    }
  })();

  return new Response(transform.readable, {
    headers: { 'Content-Type': 'application/x-ndjson', 'Cache-Control': 'no-cache' },
  });
}
