#!/usr/bin/env python3
"""Agent for testing race condition (TOCTOU) vulnerabilities in web applications."""

import argparse
import json
import threading
from datetime import datetime, timezone

try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    HAS_REQUESTS = False


def send_concurrent_requests(url, method, data, headers, count, results_list):
    """Send multiple identical requests concurrently to trigger race conditions."""
    if not HAS_REQUESTS:
        return
    barrier = threading.Barrier(count)

    def worker(idx):
        try:
            barrier.wait(timeout=5)
            if method == "POST":
                resp = requests.post(url, json=data, headers=headers, timeout=15, verify=False)
            elif method == "PUT":
                resp = requests.put(url, json=data, headers=headers, timeout=15, verify=False)
            else:
                resp = requests.get(url, headers=headers, timeout=15, verify=False)
            results_list.append({
                "thread": idx,
                "status_code": resp.status_code,
                "response_length": len(resp.content),
                "response_preview": resp.text[:200],
                "elapsed": resp.elapsed.total_seconds(),
            })
        except Exception as e:
            results_list.append({"thread": idx, "error": str(e)[:100]})

    threads = []
    for i in range(count):
        t = threading.Thread(target=worker, args=(i,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join(timeout=20)


def analyze_results(results):
    """Analyze concurrent request results for race condition indicators."""
    indicators = []
    success_count = sum(1 for r in results if r.get("status_code") == 200)
    if success_count > 1:
        indicators.append(f"{success_count} successful responses (expected 1 for idempotent operations)")

    response_bodies = [r.get("response_preview", "") for r in results if r.get("status_code") == 200]
    unique_bodies = set(response_bodies)
    if len(unique_bodies) > 1:
        indicators.append(f"Different response bodies across concurrent requests: {len(unique_bodies)} unique")

    status_codes = [r.get("status_code") for r in results if r.get("status_code")]
    if len(set(status_codes)) > 1:
        indicators.append(f"Mixed status codes: {set(status_codes)}")

    return indicators


def test_race_condition(url, method, data, token, concurrency):
    """Execute race condition test."""
    headers = {"Authorization": f"Bearer {token}"} if token else {}
    headers["Content-Type"] = "application/json"
    results = []
    send_concurrent_requests(url, method, data, headers, concurrency, results)
    indicators = analyze_results(results)
    return {
        "url": url,
        "method": method,
        "concurrency": concurrency,
        "responses": results,
        "race_indicators": indicators,
        "potential_race": len(indicators) > 0,
    }


def main():
    parser = argparse.ArgumentParser(
        description="Test race condition vulnerabilities (authorized testing only)"
    )
    parser.add_argument("--url", required=True, help="Target URL")
    parser.add_argument("--method", default="POST", choices=["GET", "POST", "PUT"])
    parser.add_argument("--data", default="{}", help="JSON payload")
    parser.add_argument("--token", help="Bearer token")
    parser.add_argument("--concurrency", type=int, default=10, help="Concurrent requests")
    parser.add_argument("--output", "-o", help="Output JSON report")
    args = parser.parse_args()

    print("[*] Race Condition Testing Agent")
    print("[!] For authorized security testing only")
    data = json.loads(args.data)
    result = test_race_condition(args.url, args.method, data, args.token, args.concurrency)

    report = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "test_result": result,
        "risk_level": "HIGH" if result["potential_race"] else "LOW",
    }
    print(f"[*] Race condition detected: {result['potential_race']}")

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"[*] Report saved to {args.output}")
    else:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
