import json
import uuid
import random
import time
import requests
from collections import Counter


API_URL = "http://127.0.0.1:8000/validate"

# --- Known values from data/blocklist_ips.csv and data/blocklist_user_agents.csv ---
BLOCKLISTED_IPS = ["192.0.2.55", "10.10.10.10", "203.0.113.25"]
BAD_UA = "python-requests/2.31.0"

CLEAN_UAS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 "
    "(KHTML, like Gecko) Version/17.5 Safari/605.1.15",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 "
    "(KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
]


def random_ip():
    """A fresh, never-repeated IP so velocity/duplicate never accidentally trigger."""
    return f"{random.randint(20, 223)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(1, 254)}"


# A small pool of NON-blocklisted IPs used only for the 'suspicious' bucket.
# Warmed up once so bad_ua + duplicate + high_velocity are all true, giving
# rule_score=50 (max, no blocklist_hit) -> blended score always lands in the
# 30-70 Suspicious band, and can NEVER cross into Blocked (50*0.7+100*0.3=65).
SUSPICIOUS_IP_POOL = [f"198.51.100.{n}" for n in range(60, 70)]


def create_openrtb_request(ip, user_agent, tag):
    return {
        "id": f"{tag}_{uuid.uuid4().hex[:8]}",
        "imp": [{"id": "1", "banner": {"format": [{"w": 300, "h": 250}]}}],
        "device": {"ip": ip, "ua": user_agent, "geo": {"country": "US"}},
        "user": {"id": f"user_{uuid.uuid4().hex[:8]}"},
        "site": {"domain": "example.com"},
        "at": 1,
    }


def send_request(request_data):
    response = requests.post(API_URL, json=request_data, timeout=10)
    response.raise_for_status()
    return response.json()


def warm_up_state():
    """
    Fire a few quick requests per shared IP pool so Redis registers
    high_velocity + duplicate BEFORE the main batch starts. After this:
      - blocklisted IPs: blocklist_hit+bad_ua+velocity+duplicate=90 -> Blocked
      - suspicious IPs : bad_ua+velocity+duplicate=50 (no blocklist) -> Suspicious
    Any further request from these IP+UA pairs keeps landing in the same
    band for the rest of the 60s window (the window keeps refreshing as
    long as we keep sending periodically).
    """
    print("Warming up blocklisted IPs (for Blocked verdicts)...")
    for ip in BLOCKLISTED_IPS:
        for _ in range(3):
            send_request(create_openrtb_request(ip, BAD_UA, "warmup"))
            time.sleep(0.15)

    print("Warming up suspicious IP pool (for stable Suspicious verdicts)...")
    for ip in SUSPICIOUS_IP_POOL:
        for _ in range(2):
            send_request(create_openrtb_request(ip, BAD_UA, "warmup"))
            time.sleep(0.15)


def build_batch(total, clean_pct, suspicious_pct, blocked_pct):
    n_clean = round(total * clean_pct / 100)
    n_suspicious = round(total * suspicious_pct / 100)
    n_blocked = total - n_clean - n_suspicious  # remainder, avoids rounding gaps

    plan = (
        ["clean"] * n_clean
        + ["suspicious"] * n_suspicious
        + ["blocked"] * n_blocked
    )
    random.shuffle(plan)
    return plan, n_clean, n_suspicious, n_blocked


def make_request_for(category):
    if category == "clean":
        # Fresh IP + normal browser UA every time -> no signals ever trigger.
        ip = random_ip()
        ua = random.choice(CLEAN_UAS)
    elif category == "suspicious":
        # Reuse the pre-warmed, non-blocklisted IP pool: bad_ua + velocity +
        # duplicate = rule_score 50 (max, no blocklist_hit), which can never
        # cross into Blocked (50*0.7 + 100*0.3 = 65 < 70).
        ip = random.choice(SUSPICIOUS_IP_POOL)
        ua = BAD_UA
    else:  # blocked
        ip = random.choice(BLOCKLISTED_IPS)
        ua = BAD_UA
    return create_openrtb_request(ip, ua, category)


def run_batch(total=100, clean_pct=50, suspicious_pct=30, blocked_pct=20):
    assert clean_pct + suspicious_pct + blocked_pct == 100, "percentages must add up to 100"

    warm_up_state()

    plan, n_clean, n_suspicious, n_blocked = build_batch(
        total, clean_pct, suspicious_pct, blocked_pct
    )

    print(f"\nGenerating {total} requests -> {n_clean} clean, {n_suspicious} suspicious, {n_blocked} blocked")
    print("=" * 70)

    expected_vs_actual = Counter()
    verdict_counts = Counter()

    for i, category in enumerate(plan, 1):
        req = make_request_for(category)
        result = send_request(req)
        verdict = result["verdict"]
        verdict_counts[verdict] += 1
        expected_vs_actual[(category, verdict)] += 1

        print(
            f"[{i:>3}/{total}] intended={category:<10} "
            f"risk_score={result['risk_score']:<6} verdict={verdict:<10} "
            f"features={result['contributing_features']}"
        )

        time.sleep(0.05)

    print("\n" + "=" * 70)
    print("SUMMARY")
    print("=" * 70)
    print(f"Requested split : Clean {clean_pct}%  Suspicious {suspicious_pct}%  Blocked {blocked_pct}%")
    print(f"Requested counts: Clean {n_clean}  Suspicious {n_suspicious}  Blocked {n_blocked}")
    print(f"\nActual verdicts returned by the server:")
    for verdict in ["Clean", "Suspicious", "Blocked"]:
        count = verdict_counts.get(verdict, 0)
        pct = (count / total) * 100
        print(f"  {verdict:<12}: {count:>3}  ({pct:.1f}%)")

    mismatches = {k: v for k, v in expected_vs_actual.items() if k[0].capitalize() != k[1]}
    if mismatches:
        print(f"\nIntended-vs-actual mismatches (expected, due to shared IP/UA state):")
        for (intended, actual), count in mismatches.items():
            print(f"  intended={intended:<10} -> actual={actual:<10} : {count} request(s)")
    else:
        print("\nEvery request's intended category matched its actual verdict.")


if __name__ == "__main__":
    run_batch(total=100, clean_pct=50, suspicious_pct=30, blocked_pct=20)