from __future__ import annotations
import asyncio
from dataclasses import dataclass, field

from .protocol import constants as C
from .protocol import wire
from .protocol.client import send_client_hello


@dataclass
class ClientProfile:
    name: str
    client_hello_version: int
    cipher_suites: list[int]
    versions: list[int] | None = None  # for supported_versions ext (TLS 1.3 capable)
    groups: list[int] = field(default_factory=lambda: [0x001d, 0x0017, 0x0018])


PROFILES: list[ClientProfile] = [
    ClientProfile(
        name="Android 4.4.2",
        client_hello_version=C.TLS_1_0,
        cipher_suites=[0xc013, 0xc014, 0x002f, 0x0035, 0x000a, 0x0004, 0x0005],
    ),
    ClientProfile(
        name="Android 7.0",
        client_hello_version=C.TLS_1_2,
        cipher_suites=[0xc02b, 0xc02f, 0xc030, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035],
    ),
    ClientProfile(
        name="Android 12",
        client_hello_version=C.TLS_1_2,
        versions=[C.TLS_1_3, C.TLS_1_2],
        cipher_suites=[0x1301, 0x1303, 0x1302, 0xc02b, 0xc02f, 0xcca9, 0xcca8, 0xc030, 0xc013, 0xc014, 0x009c, 0x009d],
    ),
    ClientProfile(
        name="Chrome 49",
        client_hello_version=C.TLS_1_2,
        cipher_suites=[0xc02b, 0xc02f, 0x009c, 0x009e, 0xc00a, 0xc009, 0xc013, 0xc014, 0x0033, 0x0039, 0x002f, 0x0035, 0x000a],
    ),
    ClientProfile(
        name="Chrome 120",
        client_hello_version=C.TLS_1_2,
        versions=[C.TLS_1_3, C.TLS_1_2],
        cipher_suites=[0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xcca9, 0xcca8, 0xc02c, 0xc030, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035],
    ),
    ClientProfile(
        name="Firefox 115",
        client_hello_version=C.TLS_1_2,
        versions=[C.TLS_1_3, C.TLS_1_2],
        cipher_suites=[0x1301, 0x1303, 0x1302, 0xc02b, 0xc02f, 0xcca9, 0xcca8, 0xc030, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035],
    ),
    ClientProfile(
        name="Safari 14",
        client_hello_version=C.TLS_1_2,
        versions=[C.TLS_1_3, C.TLS_1_2],
        cipher_suites=[0x1301, 0x1302, 0x1303, 0xc02c, 0xc02b, 0xcca9, 0xc030, 0xc02f, 0xcca8, 0xc024, 0xc023, 0xc00a, 0xc009, 0xc028, 0xc027, 0xc014, 0xc013, 0x009d, 0x009c, 0x003d, 0x003c, 0x0035, 0x002f],
    ),
    ClientProfile(
        name="IE 8 (Win XP)",
        client_hello_version=C.SSL_3_0,
        cipher_suites=[0x0004, 0x0005, 0x000a, 0x002f, 0x0035],
    ),
    ClientProfile(
        name="IE 11 (Win 8.1)",
        client_hello_version=C.TLS_1_2,
        cipher_suites=[0xc030, 0xc02f, 0xc014, 0xc013, 0x009f, 0x009e, 0x0035, 0x002f, 0x000a],
    ),
    ClientProfile(
        name="Edge 120",
        client_hello_version=C.TLS_1_2,
        versions=[C.TLS_1_3, C.TLS_1_2],
        cipher_suites=[0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xcca9, 0xcca8, 0xc030, 0x009c, 0x009d, 0x002f, 0x0035],
    ),
    ClientProfile(
        name="Java 8u291",
        client_hello_version=C.TLS_1_2,
        cipher_suites=[0xc02b, 0xc02f, 0xc02c, 0xc030, 0xcca9, 0xcca8, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035],
    ),
    ClientProfile(
        name="Java 17",
        client_hello_version=C.TLS_1_2,
        versions=[C.TLS_1_3, C.TLS_1_2],
        cipher_suites=[0x1302, 0x1303, 0x1301, 0xc02c, 0xc02b, 0xc030, 0xc02f, 0xcca9, 0xcca8, 0x009d, 0x009c, 0x0035, 0x002f],
    ),
]


@dataclass
class SimResult:
    client: str
    connected: bool
    negotiated_version: str = ""
    negotiated_cipher: str = ""
    error: str | None = None


async def simulate(host: str, port: int, sni: str | None) -> list[SimResult]:
    results: list[SimResult] = []
    for p in PROFILES:
        exts_parts = []
        if sni and not _is_ip(sni):
            try:
                exts_parts.append(wire.ext_server_name(sni))
            except Exception:
                pass
        exts_parts.append(wire.ext_ec_point_formats())
        exts_parts.append(wire.ext_supported_groups(p.groups))
        exts_parts.append(wire.ext_signature_algorithms([
            0x0403, 0x0804, 0x0401, 0x0503, 0x0805, 0x0501, 0x0603, 0x0806, 0x0601,
        ]))
        exts_parts.append(wire.ext_renegotiation_info_empty())
        if p.versions and C.TLS_1_3 in p.versions:
            exts_parts.append(wire.ext_supported_versions_client(p.versions))
            exts_parts.append(wire.ext_psk_key_exchange_modes())
            exts_parts.append(wire.ext_key_share_empty())
        exts = b"".join(exts_parts)

        res = await send_client_hello(
            host, port,
            record_version=C.TLS_1_0,
            client_hello_version=p.client_hello_version,
            cipher_suites=p.cipher_suites,
            extensions=exts,
            sni=sni,
        )
        sh = res.server_hello
        if not res.connected or sh is None or sh.alert is not None:
            results.append(SimResult(client=p.name, connected=False, error=res.error or "handshake failed"))
            continue
        v = sh.negotiated_version or sh.server_version
        version_name = C.PROTOCOL_NAMES.get(v, f"0x{v:04x}")
        cipher_name = C.CIPHER_SUITES.get(sh.cipher_suite, f"0x{sh.cipher_suite:04x}") if sh.cipher_suite else ""
        results.append(SimResult(
            client=p.name,
            connected=True,
            negotiated_version=version_name,
            negotiated_cipher=cipher_name,
        ))
    return results


def _is_ip(host: str) -> bool:
    import ipaddress
    try:
        ipaddress.ip_address(host.strip("[]"))
        return True
    except ValueError:
        return False
