#!/usr/bin/env python3
"""Bulk correlation: find the networks hiding inside a list of domains.

Companion script for the blog post. Submits a domain list to POST /correlate
(2-100 domains; already-analyzed domains are reused from cache), polls
GET /correlate/{correlation_id} until completion, and prints the clusters.

    export WHOISGENIUS_API_KEY=wg_...
    python correlate_networks.py domains_sample.txt

Billing: 1 credit per uncached domain + 1 credit for the correlation compute,
with automatic refunds if the job fails or completes degraded.

The typical SEO loop this enables:
1. Export the referring domains of a competitor (or your own site) from
   Ahrefs/Semrush/GSC.
2. bulk_analyze.py the list to get per-domain owners.
3. correlate_networks.py the list to find which of those "different sites"
   are actually the same operator: PBNs, satellite blogs, owned properties.
"""

from __future__ import annotations

import os
import sys
import time

import httpx

API = "https://api.whoisgeni.us"
KEY = os.environ["WHOISGENIUS_API_KEY"]
HEADERS = {"X-API-Key": KEY, "Content-Type": "application/json"}


def load_domains(path: str) -> list[str]:
    with open(path, encoding="utf-8") as fh:
        return [
            line.strip()
            for line in fh
            if line.strip() and not line.startswith("#")
        ]


def main() -> None:
    domains = load_domains(sys.argv[1] if len(sys.argv) > 1 else "domains_sample.txt")
    if not 2 <= len(domains) <= 100:
        sys.exit("POST /correlate needs between 2 and 100 domains")

    with httpx.Client(timeout=60) as client:
        submit = client.post(
            f"{API}/correlate", headers=HEADERS, json={"domains": domains}
        ).json()["data"]
        print(
            f"submitted {submit['domain_count']} domains "
            f"({submit['domains_cached']} cached, {submit['domains_to_analyze']} to analyze, "
            f"{submit['credits_charged']} credits)"
        )
        poll_url = f"{API}{submit['poll_url']}"  # poll HERE, not /jobs/{id}

        while True:
            payload = client.get(poll_url, headers=HEADERS).json()["data"]
            status = payload["status"]
            if status == "completed":
                break
            if status == "failed":
                sys.exit(f"correlation failed: {payload.get('error', 'unknown')}")
            print(f"  {status} ({payload.get('phase') or '...'})")
            time.sleep(15)

    result = payload["result"]  # clusters/pairwise/unclustered live under result
    if result.get("degraded"):
        print("WARNING: degraded result, some domains were dropped, credits refunded")

    for cluster in result.get("clusters", []):
        members = ", ".join(cluster["domains"])
        print(f"\ncluster ({cluster['confidence']:.0%} confidence): {members}")
        for sig in cluster.get("shared_signals", []):
            print(f"  [{sig['relation']:5}] {sig['signal_type']}: {sig['value']}")

    for domain in result.get("unclustered_domains", []):
        print(f"\nunclustered: {domain}")
    for domain in result.get("excluded_domains", []):
        print(f"excluded:    {domain}")
    if result.get("correlation_summary"):
        print(f"\nsummary: {result['correlation_summary']}")


if __name__ == "__main__":
    main()
