import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { validateRpki } from '../../db/rpki-client';

interface PrefixChangesQuery {
  asn?: string;
  from?: string;
  to?: string;
  hours?: string;
}

async function fetchWithRetry(url: string, retries = 1, timeout = 8000): Promise<any> {
  for (let i = 0; i <= retries; i++) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    try {
      const response = await fetch(url, { signal: controller.signal });
      clearTimeout(timeoutId);
      if (!response.ok) {
        if (i === retries) throw new Error(`HTTP ${response.status}`);
        continue;
      }
      return await response.json();
    } catch (e) {
      clearTimeout(timeoutId);
      if (i === retries) throw e;
      await new Promise(r => setTimeout(r, 1500));
    }
  }
}

export async function prefixChangesRoutes(fastify: FastifyInstance): Promise<void> {
  fastify.get<{ Querystring: PrefixChangesQuery }>(
    '/prefix-changes',
    async (request: FastifyRequest<{ Querystring: PrefixChangesQuery }>, reply: FastifyReply) => {
      const rawAsn = (request.query.asn || '').replace(/[^0-9]/g, '');
      
      reply.header('Access-Control-Allow-Origin', '*');
      reply.header('Cache-Control', 'no-store');

      if (!rawAsn) {
        return reply.status(400).send({ error: 'Missing ASN' });
      }

      const fromParam = request.query.from;
      const toParam = request.query.to;
      const hoursParam = Math.min(parseInt(request.query.hours || '1', 10), 168);
      
      let starttime: string;
      let endtime: string;
      
      if (fromParam && toParam) {
        starttime = new Date(fromParam).toISOString();
        endtime = new Date(toParam).toISOString();
      } else {
        endtime = new Date().toISOString();
        starttime = new Date(Date.now() - hoursParam * 3600000).toISOString();
      }

      try {
        const updUrl = `https://stat.ripe.net/data/bgp-updates/data.json?resource=AS${rawAsn}&starttime=${encodeURIComponent(starttime)}&endtime=${encodeURIComponent(endtime)}&limit=1000`;
        const raw = await fetchWithRetry(updUrl, 1, 8000);
        const updates = (raw && raw.data && raw.data.updates && raw.data.updates.updates) || [];

        const announcements: any[] = [];
        const withdrawals: any[] = [];
        const originChanges: any[] = [];
        const rpkiIssues: any[] = [];
        
        const lastOriginByPrefix: Record<string, number | null> = {};
        const rpkiSeen = new Set<string>();

        for (const u of updates) {
          const prefix = u.attrs && u.attrs.prefix; 
          if (!prefix) continue;
          
          const originRaw = u.attrs && u.attrs.origin;
          const origin = originRaw ? parseInt(String(originRaw).replace('AS', ''), 10) : null;
          const ts = u.timestamp || '';
          const peer = u.peer || '';

          if (u.type === 'A') {
            let rpkiStatus = 'unknown';
            try {
              if (origin && prefix) {
                const rpkiResult = await validateRpki(prefix, origin);
                rpkiStatus = rpkiResult.status;
              }
            } catch (e: any) {
              console.error("[Prefix Changes] RPKI lookup error:", e.message);
            }
            
            announcements.push({ prefix, timestamp: ts, peer, origin, rpki_status: rpkiStatus });

            if (lastOriginByPrefix[prefix] !== undefined && lastOriginByPrefix[prefix] !== origin) {
              originChanges.push({ prefix, from_origin: lastOriginByPrefix[prefix], to_origin: origin, timestamp: ts, peer });
            }
            lastOriginByPrefix[prefix] = origin;

            if (!rpkiSeen.has(prefix) && rpkiStatus !== 'valid' && rpkiStatus !== 'unknown' && rpkiStatus !== 'not-found') {
              rpkiSeen.add(prefix);
              rpkiIssues.push({ prefix, origin, rpki_status: rpkiStatus, timestamp: ts });
            }
          } else if (u.type === 'W') {
            withdrawals.push({ prefix, timestamp: ts, peer });
          }
        }

        return reply.status(200).send({
          asn: parseInt(rawAsn, 10),
          time_range: { from: starttime, to: endtime },
          total_updates: updates.length,
          summary: { 
            announcements: announcements.length, 
            withdrawals: withdrawals.length, 
            origin_changes: originChanges.length, 
            rpki_issues: rpkiIssues.length 
          },
          announcements,
          withdrawals,
          origin_changes: originChanges,
          rpki_issues: rpkiIssues,
        });
      } catch (e: any) {
        return reply.status(500).send({ error: e.message });
      }
    }
  );
}
