/**
 * @module aspa/generator
 * Detect upstream providers from BGP data and produce an ASPA submission guide.
 *
 * Analyzes BGP path data to detect upstream provider relationships, then
 * produces a plain-language provider list plus per-RIR instructions for
 * creating the actual ASPA object. Real ASPA objects are signed RPKI/CMS
 * objects created through your RIR's hosted RPKI platform (or a delegated
 * RPKI CA); they are not RPSL text and cannot be submitted through the
 * RIPE Database (whois) the way an aut-num or route object can. There is
 * no `aspa:` or `upstream:` attribute in the RIPE DB schema.
 *
 * @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
 *
 * @example
 * ```typescript
 * const providers = detectProviders(64501, bgpPaths);
 * console.log(providers);
 * // [{ asn: 174, name: "Cogent", confidence: 0.95, pathCount: 42 }]
 *
 * const guide = generateASPASubmissionGuide(64501, providers);
 * console.log(guide);
 * // Provider list + a link to the RIR's actual RPKI dashboard
 * ```
 */

// ── Types ───────────────────────────────────────────────

/** A BGP path observation used for provider inference */
export interface BGPPath {
  /** The AS path from collector perspective (leftmost = collector peer) */
  readonly asPath: ReadonlyArray<number>;
  /** IP prefix associated with this path */
  readonly prefix: string;
  /** The collector or vantage point that observed this path */
  readonly collector: string;
  /** When this path was observed */
  readonly timestamp: string;
}

/** A detected upstream provider with confidence scoring */
export interface Provider {
  /** The provider ASN */
  readonly asn: number;
  /** Human-readable name (if available) */
  readonly name: string;
  /** Confidence that this is a true provider (0.0 to 1.0) */
  readonly confidence: number;
  /** Number of BGP paths supporting this inference */
  readonly pathCount: number;
  /** Address families observed */
  readonly afi: ReadonlyArray<"ipv4" | "ipv6">;
}

// ── Provider Detection ──────────────────────────────────

/**
 * Detect upstream providers for an ASN by analyzing BGP path data.
 *
 * Uses the valley-free routing model: in a typical BGP path, a customer AS
 * appears to the right of its provider. By counting how often each AS appears
 * immediately to the left of the target ASN across many paths, we can infer
 * provider relationships with high confidence.
 *
 * Heuristics applied:
 * 1. An AS appearing left of the target in many paths is likely a provider.
 * 2. Higher path counts yield higher confidence.
 * 3. ASNs that only appear in a single path are treated as low-confidence.
 *
 * @param asn - The ASN to detect providers for
 * @param bgpPaths - Collection of observed BGP paths
 * @returns Sorted array of detected providers (highest confidence first)
 *
 * @example
 * ```typescript
 * const paths: BGPPath[] = [
 *   { asPath: [3356, 174, 64501], prefix: "192.0.2.0/24", collector: "rrc00", timestamp: "2026-03-26T00:00:00Z" },
 *   { asPath: [6939, 174, 64501], prefix: "192.0.2.0/24", collector: "rrc01", timestamp: "2026-03-26T00:00:00Z" },
 *   { asPath: [13335, 64501], prefix: "192.0.2.0/24", collector: "rrc03", timestamp: "2026-03-26T00:00:00Z" },
 * ];
 *
 * const providers = detectProviders(64501, paths);
 * // [
 * //   { asn: 174, name: "Unknown", confidence: 0.9, pathCount: 2, afi: ["ipv4"] },
 * //   { asn: 13335, name: "Unknown", confidence: 0.7, pathCount: 1, afi: ["ipv4"] },
 * // ]
 * ```
 */
export function detectProviders(
  asn: number,
  bgpPaths: ReadonlyArray<BGPPath>
): ReadonlyArray<Provider> {
  // Count occurrences of each ASN appearing immediately left of the target
  const providerCounts = new Map<number, { count: number; afiSet: Set<string> }>();

  for (const path of bgpPaths) {
    const { asPath, prefix } = path;
    const afi = prefix.includes(":") ? "ipv6" : "ipv4";

    for (let i = 1; i < asPath.length; i++) {
      if (asPath[i] === asn && asPath[i - 1] !== asn) {
        const providerAsn = asPath[i - 1];
        const existing = providerCounts.get(providerAsn);
        if (existing) {
          existing.count++;
          existing.afiSet.add(afi);
        } else {
          providerCounts.set(providerAsn, {
            count: 1,
            afiSet: new Set([afi]),
          });
        }
      }
    }
  }

  if (providerCounts.size === 0) {
    return [];
  }

  // Calculate confidence based on path count relative to max
  const maxCount = Math.max(...Array.from(providerCounts.values()).map((v) => v.count));

  const providers: Provider[] = [];
  for (const [providerAsn, data] of providerCounts) {
    // Confidence formula: normalized count with a floor of 0.3 for single observations
    const rawConfidence = data.count / maxCount;
    const confidence = Math.max(0.3, Math.min(1.0, rawConfidence * 0.9 + 0.1));

    providers.push({
      asn: providerAsn,
      name: "Unknown", // Name resolution requires external lookup
      confidence: Math.round(confidence * 100) / 100,
      pathCount: data.count,
      afi: Array.from(data.afiSet).sort() as Array<"ipv4" | "ipv6">,
    });
  }

  // Sort by confidence descending, then by path count descending
  return providers.sort((a, b) => {
    if (b.confidence !== a.confidence) return b.confidence - a.confidence;
    return b.pathCount - a.pathCount;
  });
}

// ── ASPA Provider Summary ────────────────────────────────

/**
 * Summarize detected providers in plain language, in the ASPA object's
 * logical field order (customerASID, then providers ascending by ASN,
 * per draft-ietf-sidrops-aspa-profile).
 *
 * This is a human-readable summary for review, not a submittable object.
 * There is no text format you can paste into a registry to create an
 * ASPA object; it must be created through your RIR's RPKI platform.
 *
 * @param asn - The customer ASN
 * @param providers - Detected upstream providers
 * @returns Plain-language summary of the ASPA object's intended content
 *
 * @example
 * ```typescript
 * const text = summarizeASPAObject(64501, [
 *   { asn: 174, name: "Cogent", confidence: 0.95, pathCount: 42, afi: ["ipv4", "ipv6"] },
 * ]);
 * console.log(text);
 * // Customer AS64501 would authorize: AS174 (Cogent, confidence 95%, seen in 42 paths)
 * ```
 */
export function summarizeASPAObject(
  asn: number,
  providers: ReadonlyArray<Provider>
): string {
  const lines: string[] = [
    `# ASPA summary for AS${asn}`,
    `# Generated by PeerCortex on ${new Date().toISOString()}`,
    `# Based on BGP path analysis. Review before creating the real object.`,
    `#`,
    `# ASPA (Autonomous System Provider Authorization) declares which ASNs`,
    `# are authorized upstream providers of this AS. This helps prevent`,
    `# route leaks by allowing RPKI validators to verify AS path legitimacy.`,
    `#`,
    `# Reference: IETF draft-ietf-sidrops-aspa-profile (not yet an RFC).`,
    `# This is a summary for review only. It is NOT a submittable object.`,
    `# ASPA objects are signed RPKI objects created through your RIR's`,
    `# hosted RPKI platform, not RPSL text you paste into a registry.`,
    ``,
    `Customer: AS${asn}`,
  ];

  const sortedProviders = [...providers].sort((a, b) => a.asn - b.asn);
  for (const provider of sortedProviders) {
    const afiStr = provider.afi.join(", ");
    const comment = `${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%, seen in ${provider.pathCount} paths, ${afiStr})`;
    lines.push(`  Provider: AS${provider.asn}  # ${comment}`);
  }

  lines.push(``);

  return lines.join("\n");
}

/** Which Regional Internet Registry an ASN belongs to, for RIR-specific guidance */
export type RIR = "ripe" | "arin" | "apnic" | "lacnic" | "afrinic";

/** Per-RIR ASPA production status and the correct place to create the object */
const RIR_ASPA_INFO: Record<
  RIR,
  { readonly status: string; readonly howTo: string; readonly infoUrl: string }
> = {
  ripe: {
    status: "Production since 15 Dec 2025",
    howTo: "RIPE NCC LIR Portal, RPKI Dashboard, ASPA section",
    infoUrl: "https://www.ripe.net/manage-ips-and-asns/resource-management/rpki/aspa/",
  },
  arin: {
    status: "Production since 20 Jan 2026",
    howTo: "ARIN Online, Routing Security menu, ASPA section",
    infoUrl: "https://www.arin.net/resources/manage/rpki/aspa/",
  },
  apnic: {
    status: "Production (exact launch date not published by APNIC; blog post from 9 Jul 2026 confirms support is live)",
    howTo: "MyAPNIC or the APNIC Registry API",
    infoUrl: "https://help.apnic.net/s/article/ASPA",
  },
  lacnic: {
    status: "Not yet in production. Committed for end of 2026",
    howTo: "Not yet available",
    infoUrl: "https://blog.lacnic.net/en/programa-rpki-nro-resumen/",
  },
  afrinic: {
    status: "Not yet in production, no committed date",
    howTo: "Not yet available",
    infoUrl: "https://blog.afrinic.net/nro-rpki-program-2025-in-review",
  },
};

/**
 * Produce a submission guide for an ASN's detected providers.
 *
 * This does NOT produce a pasteable object, because ASPA objects are
 * signed RPKI objects, not RPSL text. It produces the provider list to
 * review plus the correct place to actually create the object for the
 * given RIR.
 *
 * @param asn - The customer ASN
 * @param providers - Detected upstream providers
 * @param rir - Which RIR this ASN is registered with
 * @returns Human-readable guide: provider list plus RIR-specific instructions
 *
 * @example
 * ```typescript
 * const guide = generateASPASubmissionGuide(
 *   13335,
 *   [{ asn: 174, name: "Cogent", confidence: 0.95, pathCount: 100, afi: ["ipv4", "ipv6"] }],
 *   "ripe"
 * );
 * ```
 */
export function generateASPASubmissionGuide(
  asn: number,
  providers: ReadonlyArray<Provider>,
  rir: RIR
): string {
  const info = RIR_ASPA_INFO[rir];
  const lines: string[] = [
    `# ============================================================`,
    `# ASPA Submission Guide for AS${asn}`,
    `# Generated by PeerCortex, ${new Date().toISOString()}`,
    `# ============================================================`,
    `#`,
    `# ASPA support at your RIR (${rir.toUpperCase()}): ${info.status}`,
    `# Where to create the object: ${info.howTo}`,
    `# More info: ${info.infoUrl}`,
    `#`,
    `# ASPA objects are signed RPKI objects. There is no text format`,
    `# to paste into a registry database. Use the RIR platform above`,
    `# and enter the provider ASNs listed below.`,
    `#`,
    ``,
    `Customer: AS${asn}`,
  ];

  const highConfidence = providers.filter((p) => p.confidence >= 0.5);
  const sortedHigh = [...highConfidence].sort((a, b) => a.asn - b.asn);
  for (const provider of sortedHigh) {
    const afiComment =
      provider.afi.length === 2 ? "" : `  # ${provider.afi[0]} only`;
    lines.push(`  Provider to add: AS${provider.asn}${afiComment}`);
  }

  lines.push(``);

  const lowConfidence = providers.filter((p) => p.confidence < 0.5);
  if (lowConfidence.length > 0) {
    lines.push(`# The following providers were detected with low confidence.`);
    lines.push(`# Review before adding them:`);
    const sortedLow = [...lowConfidence].sort((a, b) => a.asn - b.asn);
    for (const provider of sortedLow) {
      lines.push(
        `#   AS${provider.asn}  (${provider.name}, confidence: ${Math.round(provider.confidence * 100)}%)`
      );
    }
    lines.push(``);
  }

  return lines.join("\n");
}
