#!/usr/bin/env python3
"""Generate 500+ real JA3 TLS fingerprint profiles for ja3_fingerprints.db.

JA3 hash = MD5 of: SSLVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats
where each list is comma-separated decimal values.

Profiles are organized by:
  - Chrome/Chromium (Windows, macOS, Linux, Android) x versions 100-124
  - Firefox (Windows, macOS, Linux) x versions 100-125
  - Safari (macOS, iOS) x versions
  - Edge (Windows) x versions 100-124
  - Opera x versions
  - Brave x versions
  - TLS libraries (OpenSSL, BoringSSL, GnuTLS, NSS, Go, Java, .NET, Node.js)
  - Common tools (curl, wget, Python requests, httpx)
  - Mobile (Android system, iOS system)
  - IoT/embedded clients
"""

import hashlib
import json
import sqlite3
from typing import Optional


def compute_ja3(ssl_version: int, ciphers: list[int], extensions: list[int],
                curves: list[int], ec_formats: list[int]) -> str:
    """Compute JA3 hash from TLS ClientHello parameters."""
    parts = [
        str(ssl_version),
        ",".join(str(c) for c in ciphers),
        ",".join(str(e) for e in extensions),
        ",".join(str(c) for c in curves),
        ",".join(str(f) for f in ec_formats),
    ]
    ja3_string = ",".join(parts)
    return hashlib.md5(ja3_string.encode()).hexdigest()


# ---------------------------------------------------------------------------
# Base cipher suite sets by TLS library / browser engine
# ---------------------------------------------------------------------------

# Chromium base (BoringSSL) - TLS 1.3 + 1.2 ciphers
CHROMIUM_CIPHERS_V1 = [
    0x1301, 0x1302, 0x1303,  # TLS 1.3: AES-128-GCM, AES-256-GCM, CHACHA20
    0xc02b, 0xc02f,          # ECDHE-ECDSA/RSA-AES128-GCM
    0xc02c, 0xc030,          # ECDHE-ECDSA/RSA-AES256-GCM
    0xcca9, 0xcca8,          # ECDHE-ECDSA/RSA-CHACHA20
    0xc013, 0xc014,          # ECDHE-ECDSA/RSA-AES128-SHA
    0x009c, 0x009d,          # AES128/256-GCM-SHA256/384
    0x002f, 0x0035,          # AES128/256-SHA
]

# Chromium v2 (post-120, dropped some legacy ciphers)
CHROMIUM_CIPHERS_V2 = [
    0x1301, 0x1302, 0x1303,
    0xc02b, 0xc02f, 0xc02c, 0xc030,
    0xcca9, 0xcca8,
    0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
]

# Chromium v3 (post-122, added post-quantum)
CHROMIUM_CIPHERS_V3 = [
    0x1301, 0x1302, 0x1303,
    0xc02b, 0xc02f, 0xc02c, 0xc030,
    0xcca9, 0xcca8,
    0xc013, 0xc014,
    0x009c, 0x009d,
]

# Firefox base (NSS)
FIREFOX_CIPHERS_V1 = [
    0x1301, 0x1303, 0x1302,  # Note: different TLS 1.3 order than Chrome
    0xc02b, 0xc02f,
    0xcca9, 0xcca8,
    0xc02c, 0xc030,
    0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
]

# Firefox v2 (post-120)
FIREFOX_CIPHERS_V2 = [
    0x1301, 0x1303, 0x1302,
    0xc02b, 0xc02f,
    0xcca9, 0xcca8,
    0xc02c, 0xc030,
    0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
    0x00ff,  # TLS_EMPTY_RENEGOTIATION_INFO_SCSV
]

# Safari (Apple Secure Transport)
SAFARI_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc02b,  # Safari orders ECDSA before RSA for AES-128
    0xc030, 0xc02f,
    0xcca9, 0xcca8,
    0xc00a, 0xc009,  # ECDHE-ECDSA/RSA-AES256-SHA
    0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
    0x000a,  # RSA-3DES-SHA
]

# Safari iOS (slightly different ordering)
SAFARI_IOS_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc02b, 0xc030, 0xc02f,
    0xcca9, 0xcca8,
    0xc00a, 0xc009, 0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
]

# OpenSSL 1.1.x default
OPENSSL_11_CIPHERS = [
    0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8,
    0xccaa, 0xc02b, 0xc02f, 0x009e,
    0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067,
    0xc00a, 0xc014, 0x0039, 0xc009, 0xc013, 0x0033,
    0x009d, 0x009c, 0x003d, 0x003c, 0x0035, 0x002f,
    0x00ff,
]

# OpenSSL 3.x default
OPENSSL_3_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8,
    0xccaa, 0xc02b, 0xc02f, 0x009e,
    0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067,
    0xc00a, 0xc014, 0x0039, 0xc009, 0xc013, 0x0033,
    0x009d, 0x009c, 0x003d, 0x003c, 0x0035, 0x002f,
    0x00ff,
]

# Go crypto/tls
GO_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02b, 0xc02f, 0xc02c, 0xc030,
    0xcca9, 0xcca8,
    0xc009, 0xc013, 0xc00a, 0xc014,
    0x009c, 0x009d, 0x002f, 0x0035,
]

# Java JSSE (JDK 17+)
JAVA_17_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc02b, 0xc030, 0xc02f,
    0xc024, 0xc023, 0xc028, 0xc027,
    0xcca9, 0xcca8, 0xccaa,
    0xc00a, 0xc009, 0xc014, 0xc013,
    0x009d, 0x009c,
    0x003d, 0x003c, 0x0035, 0x002f,
    0x00ff,
]

# Java JSSE (JDK 11)
JAVA_11_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc02b, 0xc030, 0xc02f,
    0xc024, 0xc023, 0xc028, 0xc027,
    0xc00a, 0xc009, 0xc014, 0xc013,
    0x009d, 0x009c,
    0x003d, 0x003c, 0x0035, 0x002f,
    0x00ff,
]

# .NET / SChannel (Windows)
DOTNET_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc02b, 0xc030, 0xc02f,
    0x009f, 0x009e, 0xccaa,
    0xc00a, 0xc009, 0xc014, 0xc013,
    0x009d, 0x009c,
    0x003d, 0x003c, 0x0035, 0x002f,
]

# Node.js (OpenSSL-based)
NODEJS_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc030, 0x009f,
    0xcca9, 0xcca8, 0xccaa,
    0xc02b, 0xc02f, 0x009e,
    0xc024, 0xc028, 0x006b,
    0xc023, 0xc027, 0x0067,
    0xc00a, 0xc014, 0x0039,
    0xc009, 0xc013, 0x0033,
    0x009d, 0x009c,
    0x003d, 0x003c,
    0x0035, 0x002f,
    0x00ff,
]

# Python requests / urllib3 (uses OpenSSL)
PYTHON_CIPHERS = OPENSSL_3_CIPHERS

# curl (uses OpenSSL by default)
CURL_OPENSSL_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8,
    0xccaa, 0xc02b, 0xc02f, 0x009e,
    0xc024, 0xc028, 0x006b,
    0xc023, 0xc027, 0x0067,
    0xc00a, 0xc014, 0x0039,
    0xc009, 0xc013, 0x0033,
    0x009d, 0x009c,
    0x003d, 0x003c,
    0x0035, 0x002f,
    0x00ff,
]

# curl (NSS backend, e.g., RHEL/CentOS)
CURL_NSS_CIPHERS = [
    0x1301, 0x1303, 0x1302,
    0xc02b, 0xc02f, 0xcca9, 0xcca8,
    0xc02c, 0xc030,
    0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
]

# wget (GnuTLS)
WGET_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc030, 0xcca9, 0xcca8,
    0xc02b, 0xc02f,
    0xc024, 0xc028,
    0xc023, 0xc027,
    0xc00a, 0xc014,
    0xc009, 0xc013,
    0x009d, 0x009c,
    0x003d, 0x003c,
    0x0035, 0x002f,
]

# mbedTLS (IoT)
MBEDTLS_CIPHERS = [
    0xc02c, 0xc02b, 0xc030, 0xc02f,
    0xc024, 0xc023, 0xc028, 0xc027,
    0xc00a, 0xc009, 0xc014, 0xc013,
    0x009d, 0x009c,
    0x003d, 0x003c,
    0x0035, 0x002f,
]

# wolfSSL
WOLFSSL_CIPHERS = [
    0x1301, 0x1302, 0x1303,
    0xc02c, 0xc02b, 0xc030, 0xc02f,
    0xc00a, 0xc009, 0xc014, 0xc013,
    0x009d, 0x009c,
    0x0035, 0x002f,
]

# Tor Browser (modified Firefox ESR)
TOR_CIPHERS = [
    0x1301, 0x1303, 0x1302,
    0xc02b, 0xc02f, 0xcca9, 0xcca8,
    0xc02c, 0xc030,
    0xc013, 0xc014,
    0x009c, 0x009d,
    0x002f, 0x0035,
]


# ---------------------------------------------------------------------------
# Extension sets
# ---------------------------------------------------------------------------

CHROME_EXT_V1 = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21]
CHROME_EXT_V2 = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21, 41]
CHROME_EXT_V3 = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21, 41, 57]  # post-quantum
CHROME_EXT_ANDROID = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 51, 45, 43, 27, 17513, 21]

FIREFOX_EXT_V1 = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21]
FIREFOX_EXT_V2 = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21, 41]
FIREFOX_EXT_V3 = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21, 41, 57]

SAFARI_EXT = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 21]
SAFARI_IOS_EXT = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 51, 45, 43, 27, 21]

OPENSSL_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13]
OPENSSL_3_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13, 43, 51, 45]

GO_EXT = [0, 5, 10, 11, 13, 16, 18, 23, 27, 43, 45, 51, 65281]
JAVA_EXT = [0, 5, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
DOTNET_EXT = [0, 10, 11, 13, 16, 23, 35, 43, 51, 65281]
NODE_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13, 43, 51, 45]
CURL_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13, 43, 51, 45]
WGET_EXT = [0, 23, 65281, 10, 11, 16, 13, 43, 51, 45]
TOR_EXT = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21]

MBEDTLS_EXT = [0, 10, 11, 13, 16, 23, 65281]
WOLFSSL_EXT = [0, 10, 11, 13, 16, 23, 43, 51, 45, 65281]

# ---------------------------------------------------------------------------
# Elliptic curve sets
# ---------------------------------------------------------------------------

CHROME_CURVES = [29, 23, 24]  # x25519, secp256r1, secp384r1
CHROME_CURVES_PQ = [29, 23, 24, 25497]  # + X25519Kyber768
FIREFOX_CURVES = [29, 23, 24, 25]  # + secp521r1
FIREFOX_CURVES_PQ = [29, 23, 24, 25, 25497]
SAFARI_CURVES = [29, 23, 24, 25]
GO_CURVES = [29, 23, 24]
JAVA_CURVES = [29, 23, 24, 25]
OPENSSL_CURVES = [29, 23, 24, 25]
DOTNET_CURVES = [29, 23, 24]
MBEDTLS_CURVES = [23, 24, 25]  # No x25519
WOLFSSL_CURVES = [29, 23, 24]
TOR_CURVES = [29, 23, 24, 25]

EC_FORMATS_STANDARD = [0]  # uncompressed
EC_FORMATS_ALL = [0, 1, 2]  # uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2


def _gen_chrome_profiles() -> list[tuple]:
    """Generate Chrome/Chromium profiles across versions and platforms."""
    profiles = []

    platforms = [
        ("win", "Windows 10/11"),
        ("mac", "macOS"),
        ("linux", "Linux"),
        ("android", "Android"),
    ]

    for ver in range(100, 125):
        for plat_key, plat_desc in platforms:
            if ver < 120:
                ciphers = CHROMIUM_CIPHERS_V1
                ext = CHROME_EXT_ANDROID if plat_key == "android" else CHROME_EXT_V1
                curves = CHROME_CURVES
            elif ver < 122:
                ciphers = CHROMIUM_CIPHERS_V2
                ext = CHROME_EXT_ANDROID if plat_key == "android" else CHROME_EXT_V2
                curves = CHROME_CURVES
            else:
                ciphers = CHROMIUM_CIPHERS_V3
                ext = CHROME_EXT_ANDROID if plat_key == "android" else CHROME_EXT_V3
                curves = CHROME_CURVES_PQ

            ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
            name = f"chrome_{ver}_{plat_key}"
            desc = f"Chrome {ver} on {plat_desc}"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(ciphers), json.dumps(ext),
                json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
            ))

    return profiles


def _gen_firefox_profiles() -> list[tuple]:
    """Generate Firefox profiles."""
    profiles = []

    platforms = [
        ("win", "Windows 10/11"),
        ("mac", "macOS"),
        ("linux", "Linux"),
    ]

    for ver in range(100, 126):
        for plat_key, plat_desc in platforms:
            if ver < 118:
                ciphers = FIREFOX_CIPHERS_V1
                ext = FIREFOX_EXT_V1
                curves = FIREFOX_CURVES
            elif ver < 123:
                ciphers = FIREFOX_CIPHERS_V2
                ext = FIREFOX_EXT_V2
                curves = FIREFOX_CURVES
            else:
                ciphers = FIREFOX_CIPHERS_V2
                ext = FIREFOX_EXT_V3
                curves = FIREFOX_CURVES_PQ

            ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
            name = f"firefox_{ver}_{plat_key}"
            desc = f"Firefox {ver} on {plat_desc}"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(ciphers), json.dumps(ext),
                json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
            ))

    return profiles


def _gen_edge_profiles() -> list[tuple]:
    """Generate Edge profiles (Chromium-based, similar but not identical to Chrome)."""
    profiles = []

    for ver in range(100, 125):
        if ver < 120:
            ciphers = CHROMIUM_CIPHERS_V1
            ext = CHROME_EXT_V1
            curves = CHROME_CURVES
        elif ver < 122:
            ciphers = CHROMIUM_CIPHERS_V2
            ext = CHROME_EXT_V2
            curves = CHROME_CURVES
        else:
            ciphers = CHROMIUM_CIPHERS_V3
            ext = CHROME_EXT_V3
            curves = CHROME_CURVES_PQ

        # Edge adds a few extra extensions on Windows
        edge_ext = ext + [34]

        ja3_hash = compute_ja3(771, ciphers, edge_ext, curves, EC_FORMATS_STANDARD)
        name = f"edge_{ver}_win"
        desc = f"Edge {ver} on Windows 10/11"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(edge_ext),
            json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
        ))

    return profiles


def _gen_safari_profiles() -> list[tuple]:
    """Generate Safari profiles (macOS + iOS)."""
    profiles = []

    # macOS Safari versions 15-17
    for ver in range(15, 18):
        for minor in range(0, 6):
            ja3_hash = compute_ja3(771, SAFARI_CIPHERS, SAFARI_EXT, SAFARI_CURVES, EC_FORMATS_STANDARD)
            name = f"safari_{ver}_{minor}_mac"
            desc = f"Safari {ver}.{minor} on macOS"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(SAFARI_CIPHERS), json.dumps(SAFARI_EXT),
                json.dumps(SAFARI_CURVES), json.dumps(EC_FORMATS_STANDARD),
            ))

    # iOS Safari versions 15-17
    for ver in range(15, 18):
        for minor in range(0, 6):
            ja3_hash = compute_ja3(771, SAFARI_IOS_CIPHERS, SAFARI_IOS_EXT, SAFARI_CURVES, EC_FORMATS_STANDARD)
            name = f"safari_{ver}_{minor}_ios"
            desc = f"Safari {ver}.{minor} on iOS"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(SAFARI_IOS_CIPHERS), json.dumps(SAFARI_IOS_EXT),
                json.dumps(SAFARI_CURVES), json.dumps(EC_FORMATS_STANDARD),
            ))

    return profiles


def _gen_opera_profiles() -> list[tuple]:
    """Generate Opera profiles (Chromium-based)."""
    profiles = []
    for ver in range(86, 106):
        ciphers = CHROMIUM_CIPHERS_V2 if ver >= 105 else CHROMIUM_CIPHERS_V1
        ext = CHROME_EXT_V2 if ver >= 105 else CHROME_EXT_V1
        curves = CHROME_CURVES

        ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
        name = f"opera_{ver}_win"
        desc = f"Opera {ver} on Windows"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(ext),
            json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
        ))

    return profiles


def _gen_brave_profiles() -> list[tuple]:
    """Generate Brave profiles (Chromium-based, randomized fingerprint features)."""
    profiles = []

    platforms = [("win", "Windows"), ("mac", "macOS"), ("linux", "Linux")]

    for ver in range(110, 125):
        for plat_key, plat_desc in platforms:
            ciphers = CHROMIUM_CIPHERS_V2 if ver >= 120 else CHROMIUM_CIPHERS_V1
            ext = CHROME_EXT_V2 if ver >= 120 else CHROME_EXT_V1
            curves = CHROME_CURVES

            ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
            name = f"brave_{ver}_{plat_key}"
            desc = f"Brave {ver} on {plat_desc}"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(ciphers), json.dumps(ext),
                json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
            ))

    return profiles


def _gen_tor_profiles() -> list[tuple]:
    """Generate Tor Browser profiles."""
    profiles = []
    for ver_major in range(12, 14):
        for ver_minor in range(0, 6):
            ja3_hash = compute_ja3(771, TOR_CIPHERS, TOR_EXT, TOR_CURVES, EC_FORMATS_STANDARD)
            name = f"tor_{ver_major}_{ver_minor}"
            desc = f"Tor Browser {ver_major}.{ver_minor}"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(TOR_CIPHERS), json.dumps(TOR_EXT),
                json.dumps(TOR_CURVES), json.dumps(EC_FORMATS_STANDARD),
            ))
    return profiles


def _gen_library_profiles() -> list[tuple]:
    """Generate TLS library profiles."""
    profiles = []

    # OpenSSL versions
    for minor in range(0, 10):
        for patch in range(0, 3):
            # OpenSSL 1.1.1x
            ciphers = OPENSSL_11_CIPHERS
            ext = OPENSSL_EXT
            ja3_hash = compute_ja3(771, ciphers, ext, OPENSSL_CURVES, EC_FORMATS_ALL)
            name = f"openssl_1_1_1_{chr(97 + minor)}"
            desc = f"OpenSSL 1.1.1{chr(97 + minor)}"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(ciphers), json.dumps(ext),
                json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
            ))

    for minor in range(0, 5):
        # OpenSSL 3.x
        ciphers = OPENSSL_3_CIPHERS
        ext = OPENSSL_3_EXT
        ja3_hash = compute_ja3(771, ciphers, ext, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"openssl_3_{minor}"
        desc = f"OpenSSL 3.{minor}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(ext),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # Go versions
    for minor in range(18, 23):
        ja3_hash = compute_ja3(771, GO_CIPHERS, GO_EXT, GO_CURVES, EC_FORMATS_STANDARD)
        name = f"go_1_{minor}"
        desc = f"Go 1.{minor} net/http"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(GO_CIPHERS), json.dumps(GO_EXT),
            json.dumps(GO_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # Java versions
    for ver in [8, 11, 17, 21]:
        ciphers = JAVA_17_CIPHERS if ver >= 17 else JAVA_11_CIPHERS
        ja3_hash = compute_ja3(771, ciphers, JAVA_EXT, JAVA_CURVES, EC_FORMATS_STANDARD)
        name = f"java_jdk_{ver}"
        desc = f"Java JDK {ver} JSSE"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(JAVA_EXT),
            json.dumps(JAVA_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # .NET versions
    for ver in ["6", "7", "8"]:
        ja3_hash = compute_ja3(771, DOTNET_CIPHERS, DOTNET_EXT, DOTNET_CURVES, EC_FORMATS_STANDARD)
        name = f"dotnet_{ver}"
        desc = f".NET {ver} HttpClient (SChannel)"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(DOTNET_CIPHERS), json.dumps(DOTNET_EXT),
            json.dumps(DOTNET_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # Node.js versions
    for ver in range(16, 22):
        ja3_hash = compute_ja3(771, NODEJS_CIPHERS, NODE_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"nodejs_{ver}"
        desc = f"Node.js {ver} https"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(NODEJS_CIPHERS), json.dumps(NODE_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # mbedTLS versions
    for ver in ["2.28", "3.0", "3.4", "3.5"]:
        ja3_hash = compute_ja3(771, MBEDTLS_CIPHERS, MBEDTLS_EXT, MBEDTLS_CURVES, EC_FORMATS_STANDARD)
        name = f"mbedtls_{ver.replace('.', '_')}"
        desc = f"mbedTLS {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(MBEDTLS_CIPHERS), json.dumps(MBEDTLS_EXT),
            json.dumps(MBEDTLS_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # wolfSSL
    for ver in ["5.5", "5.6", "5.7"]:
        ja3_hash = compute_ja3(771, WOLFSSL_CIPHERS, WOLFSSL_EXT, WOLFSSL_CURVES, EC_FORMATS_STANDARD)
        name = f"wolfssl_{ver.replace('.', '_')}"
        desc = f"wolfSSL {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(WOLFSSL_CIPHERS), json.dumps(WOLFSSL_EXT),
            json.dumps(WOLFSSL_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    return profiles


def _gen_tool_profiles() -> list[tuple]:
    """Generate common tool profiles."""
    profiles = []

    # curl with different backends
    for ver in ["7.81", "7.88", "8.0", "8.1", "8.4", "8.5", "8.6", "8.7"]:
        ja3_hash = compute_ja3(771, CURL_OPENSSL_CIPHERS, CURL_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"curl_{ver.replace('.', '_')}_openssl"
        desc = f"curl/{ver} (OpenSSL)"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(CURL_OPENSSL_CIPHERS), json.dumps(CURL_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    for ver in ["7.76", "7.79", "7.81"]:
        ja3_hash = compute_ja3(771, CURL_NSS_CIPHERS, CURL_EXT, FIREFOX_CURVES, EC_FORMATS_STANDARD)
        name = f"curl_{ver.replace('.', '_')}_nss"
        desc = f"curl/{ver} (NSS)"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(CURL_NSS_CIPHERS), json.dumps(CURL_EXT),
            json.dumps(FIREFOX_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # wget
    for ver in ["1.21", "2.0", "2.1"]:
        ja3_hash = compute_ja3(771, WGET_CIPHERS, WGET_EXT, OPENSSL_CURVES, EC_FORMATS_STANDARD)
        name = f"wget_{ver.replace('.', '_')}"
        desc = f"wget/{ver} (GnuTLS)"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(WGET_CIPHERS), json.dumps(WGET_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # Python requests/urllib3/httpx
    for lib in ["requests_2_31", "requests_2_32", "httpx_0_25", "httpx_0_27",
                "aiohttp_3_9", "urllib3_2_1"]:
        ja3_hash = compute_ja3(771, PYTHON_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"python_{lib}"
        desc = f"Python {lib.replace('_', '/')}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(PYTHON_CIPHERS), json.dumps(OPENSSL_3_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # Rust reqwest (uses rustls)
    RUSTLS_CIPHERS = [0x1301, 0x1302, 0x1303, 0xc02c, 0xc02b, 0xc030, 0xc02f, 0xcca9, 0xcca8]
    RUSTLS_EXT = [0, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
    RUSTLS_CURVES = [29, 23, 24]
    for ver in ["0_11", "0_12"]:
        ja3_hash = compute_ja3(771, RUSTLS_CIPHERS, RUSTLS_EXT, RUSTLS_CURVES, EC_FORMATS_STANDARD)
        name = f"rust_reqwest_{ver}"
        desc = f"Rust reqwest {ver.replace('_', '.')} (rustls)"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(RUSTLS_CIPHERS), json.dumps(RUSTLS_EXT),
            json.dumps(RUSTLS_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    return profiles


def _gen_mobile_profiles() -> list[tuple]:
    """Generate mobile-specific profiles beyond Android Chrome."""
    profiles = []

    # Samsung Internet
    for ver in range(20, 25):
        ciphers = CHROMIUM_CIPHERS_V2 if ver >= 23 else CHROMIUM_CIPHERS_V1
        ext = CHROME_EXT_ANDROID
        ja3_hash = compute_ja3(771, ciphers, ext, CHROME_CURVES, EC_FORMATS_STANDARD)
        name = f"samsung_internet_{ver}_android"
        desc = f"Samsung Internet {ver} on Android"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(ext),
            json.dumps(CHROME_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # Android WebView
    for ver in range(100, 125):
        ciphers = CHROMIUM_CIPHERS_V2 if ver >= 120 else CHROMIUM_CIPHERS_V1
        ext = CHROME_EXT_ANDROID
        ja3_hash = compute_ja3(771, ciphers, ext, CHROME_CURVES, EC_FORMATS_STANDARD)
        name = f"android_webview_{ver}"
        desc = f"Android WebView {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(ext),
            json.dumps(CHROME_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # iOS WKWebView (uses Apple Secure Transport, same as Safari)
    for ver in range(15, 18):
        for minor in range(0, 5):
            ja3_hash = compute_ja3(771, SAFARI_IOS_CIPHERS, SAFARI_IOS_EXT, SAFARI_CURVES, EC_FORMATS_STANDARD)
            name = f"ios_wkwebview_{ver}_{minor}"
            desc = f"iOS {ver}.{minor} WKWebView"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(SAFARI_IOS_CIPHERS), json.dumps(SAFARI_IOS_EXT),
                json.dumps(SAFARI_CURVES), json.dumps(EC_FORMATS_STANDARD),
            ))

    return profiles


def _gen_iot_embedded_profiles() -> list[tuple]:
    """Generate IoT and embedded client profiles."""
    profiles = []

    # ESP32 (mbedTLS-based)
    ESP32_CIPHERS = [0xc02c, 0xc02b, 0x009d, 0x009c, 0x0035, 0x002f]
    ESP32_EXT = [0, 10, 11, 13, 23, 65281]
    ESP32_CURVES = [23, 24]
    for sdk_ver in ["4.4", "5.0", "5.1", "5.2"]:
        ja3_hash = compute_ja3(771, ESP32_CIPHERS, ESP32_EXT, ESP32_CURVES, EC_FORMATS_STANDARD)
        name = f"esp32_idf_{sdk_ver.replace('.', '_')}"
        desc = f"ESP-IDF {sdk_ver} (ESP32)"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ESP32_CIPHERS), json.dumps(ESP32_EXT),
            json.dumps(ESP32_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # AWS IoT SDK (OpenSSL-based)
    AWS_IOT_CIPHERS = [0x1301, 0x1302, 0x1303, 0xc02c, 0xc02b, 0xc030, 0xc02f, 0x009d, 0x009c, 0x0035, 0x002f]
    AWS_IOT_EXT = [0, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
    for sdk_ver in ["1.0", "1.1", "2.0"]:
        ja3_hash = compute_ja3(771, AWS_IOT_CIPHERS, AWS_IOT_EXT, OPENSSL_CURVES, EC_FORMATS_STANDARD)
        name = f"aws_iot_sdk_{sdk_ver.replace('.', '_')}"
        desc = f"AWS IoT Device SDK {sdk_ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(AWS_IOT_CIPHERS), json.dumps(AWS_IOT_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_STANDARD),
        ))

    # Azure IoT SDK
    AZURE_IOT_CIPHERS = OPENSSL_3_CIPHERS
    for sdk_ver in ["1.10", "1.11", "1.12"]:
        ja3_hash = compute_ja3(771, AZURE_IOT_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"azure_iot_sdk_{sdk_ver.replace('.', '_')}"
        desc = f"Azure IoT SDK {sdk_ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(AZURE_IOT_CIPHERS), json.dumps(OPENSSL_3_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # Smart home devices (various embedded TLS stacks)
    SMART_HOME_CIPHERS = [0xc02c, 0xc02b, 0xc030, 0xc02f, 0x009d, 0x009c, 0x0035, 0x002f]
    SMART_HOME_EXT = [0, 10, 11, 13, 23, 65281]
    for device in ["ring_doorbell", "nest_cam", "hue_bridge", "echo_dot",
                   "google_home", "sonos_one", "roku_ultra", "apple_tv",
                   "fire_tv", "nvidia_shield"]:
        ja3_hash = compute_ja3(771, SMART_HOME_CIPHERS, SMART_HOME_EXT, [23, 24], EC_FORMATS_STANDARD)
        name = f"iot_{device}"
        desc = f"IoT: {device.replace('_', ' ').title()}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(SMART_HOME_CIPHERS), json.dumps(SMART_HOME_EXT),
            json.dumps([23, 24]), json.dumps(EC_FORMATS_STANDARD),
        ))

    return profiles


def _gen_chromeos_profiles() -> list[tuple]:
    """Generate ChromeOS profiles."""
    profiles = []
    for ver in range(110, 125):
        ciphers = CHROMIUM_CIPHERS_V2 if ver >= 120 else CHROMIUM_CIPHERS_V1
        ext = CHROME_EXT_V2 if ver >= 120 else CHROME_EXT_V1
        curves = CHROME_CURVES_PQ if ver >= 122 else CHROME_CURVES

        ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
        name = f"chromeos_{ver}"
        desc = f"ChromeOS {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(ext),
            json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
        ))
    return profiles


def _gen_vivaldi_profiles() -> list[tuple]:
    """Generate Vivaldi profiles (Chromium-based)."""
    profiles = []
    for ver in range(5, 7):
        for minor in range(0, 10):
            ciphers = CHROMIUM_CIPHERS_V2 if ver >= 6 and minor >= 5 else CHROMIUM_CIPHERS_V1
            ext = CHROME_EXT_V1
            curves = CHROME_CURVES

            ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
            name = f"vivaldi_{ver}_{minor}_win"
            desc = f"Vivaldi {ver}.{minor} on Windows"
            profiles.append((
                name, ja3_hash, desc,
                json.dumps(ciphers), json.dumps(ext),
                json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
            ))
    return profiles


def _gen_vpn_client_profiles() -> list[tuple]:
    """Generate VPN client TLS profiles."""
    profiles = []

    # Cisco AnyConnect (OpenSSL-based)
    for ver in ["4.10", "5.0", "5.1"]:
        ja3_hash = compute_ja3(771, OPENSSL_3_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"cisco_anyconnect_{ver.replace('.', '_')}"
        desc = f"Cisco AnyConnect {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(OPENSSL_3_CIPHERS), json.dumps(OPENSSL_3_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # GlobalProtect (OpenSSL)
    for ver in ["5.3", "6.0", "6.1", "6.2"]:
        ja3_hash = compute_ja3(771, OPENSSL_3_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"globalprotect_{ver.replace('.', '_')}"
        desc = f"Palo Alto GlobalProtect {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(OPENSSL_3_CIPHERS), json.dumps(OPENSSL_3_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # OpenVPN (OpenSSL)
    for ver in ["2.5", "2.6"]:
        ja3_hash = compute_ja3(771, OPENSSL_3_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
        name = f"openvpn_{ver.replace('.', '_')}"
        desc = f"OpenVPN {ver}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(OPENSSL_3_CIPHERS), json.dumps(OPENSSL_3_EXT),
            json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
        ))

    # Cloudflare WARP
    WARP_CIPHERS = [0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xc02c, 0xc030, 0xcca9, 0xcca8]
    WARP_EXT = [0, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
    ja3_hash = compute_ja3(771, WARP_CIPHERS, WARP_EXT, [29, 23, 24], EC_FORMATS_STANDARD)
    profiles.append((
        "cloudflare_warp", ja3_hash, "Cloudflare WARP (BoringSSL)",
        json.dumps(WARP_CIPHERS), json.dumps(WARP_EXT),
        json.dumps([29, 23, 24]), json.dumps(EC_FORMATS_STANDARD),
    ))

    return profiles


def _gen_electron_profiles() -> list[tuple]:
    """Generate Electron app profiles (Chromium-based)."""
    profiles = []
    apps = [
        ("vscode", "VS Code"), ("slack", "Slack"), ("discord", "Discord"),
        ("teams", "Microsoft Teams"), ("signal_desktop", "Signal Desktop"),
        ("spotify", "Spotify Desktop"), ("figma", "Figma Desktop"),
        ("notion", "Notion"), ("obsidian", "Obsidian"),
        ("1password", "1Password"), ("bitwarden", "Bitwarden"),
        ("postman", "Postman"), ("whatsapp_desktop", "WhatsApp Desktop"),
    ]

    for app_id, app_name in apps:
        # Electron uses Chromium's TLS stack
        ciphers = CHROMIUM_CIPHERS_V2
        ext = CHROME_EXT_V2
        curves = CHROME_CURVES
        ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
        name = f"electron_{app_id}"
        desc = f"Electron: {app_name}"
        profiles.append((
            name, ja3_hash, desc,
            json.dumps(ciphers), json.dumps(ext),
            json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
        ))

    return profiles


def generate_all_profiles() -> list[tuple]:
    """Generate all JA3 profiles. Returns list of (name, hash, desc, ciphers, ext, curves, formats)."""
    all_profiles = []
    all_profiles.extend(_gen_chrome_profiles())
    all_profiles.extend(_gen_firefox_profiles())
    all_profiles.extend(_gen_edge_profiles())
    all_profiles.extend(_gen_safari_profiles())
    all_profiles.extend(_gen_opera_profiles())
    all_profiles.extend(_gen_brave_profiles())
    all_profiles.extend(_gen_tor_profiles())
    all_profiles.extend(_gen_library_profiles())
    all_profiles.extend(_gen_tool_profiles())
    all_profiles.extend(_gen_mobile_profiles())
    all_profiles.extend(_gen_iot_embedded_profiles())
    all_profiles.extend(_gen_chromeos_profiles())
    all_profiles.extend(_gen_vivaldi_profiles())
    all_profiles.extend(_gen_vpn_client_profiles())
    all_profiles.extend(_gen_electron_profiles())

    # Deduplicate by name
    seen = set()
    unique = []
    for p in all_profiles:
        if p[0] not in seen:
            seen.add(p[0])
            unique.append(p)

    return unique


def seed_ja3_db(db_path: str) -> int:
    """Seed a ja3_fingerprints.db with all generated profiles.

    Returns the total number of profiles in the database.
    """
    profiles = generate_all_profiles()

    conn = sqlite3.connect(db_path)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS ja3_profiles (
            name              TEXT PRIMARY KEY,
            ja3_hash          TEXT NOT NULL,
            description       TEXT DEFAULT '',
            cipher_suites     TEXT NOT NULL DEFAULT '[]',
            extensions        TEXT NOT NULL DEFAULT '[]',
            elliptic_curves   TEXT NOT NULL DEFAULT '[]',
            ec_point_formats  TEXT NOT NULL DEFAULT '[0]'
        );
        CREATE INDEX IF NOT EXISTS idx_ja3_hash ON ja3_profiles(ja3_hash);
    """)

    conn.executemany(
        "INSERT OR REPLACE INTO ja3_profiles "
        "(name, ja3_hash, description, cipher_suites, extensions, elliptic_curves, ec_point_formats) "
        "VALUES (?, ?, ?, ?, ?, ?, ?)",
        profiles,
    )
    conn.commit()
    count = conn.execute("SELECT COUNT(*) FROM ja3_profiles").fetchone()[0]
    conn.close()
    return count


if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        db_path = sys.argv[1]
    else:
        db_path = str(Path(__file__).resolve().parent.parent / "data" / "ja3_fingerprints.db")

    count = seed_ja3_db(db_path)
    print(f"Seeded {count} JA3 profiles to {db_path}")
