"""Cases for the curl_cffi proxy lab. Each case is a complete script.

A case is (name, environment variables, code). The runner writes the code to <name>.py and runs
`python <name>.py` in a new interpreter with a clean environment, so a case sees only the variables
listed for it. The fixture URLs and the show() / show_error() helpers come from lab_common.py.
"""

HEADER = "from curl_cffi import requests\nfrom lab_common import *\n"
ASYNC_HEADER = "import asyncio\n\nfrom curl_cffi import AsyncSession\nfrom lab_common import *\n"


def guarded(body: str, header: str = HEADER) -> str:
    """Run the given lines inside try/except and print what they returned or raised."""
    lines = ["    " + line if line else "" for line in body.strip("\n").splitlines()]
    return header + "\ntry:\n" + "\n".join(lines) + "\nexcept Exception as error:\n    show_error(error)\n"


def get(arguments: str) -> str:
    """One requests.get(); the arguments are spliced in verbatim."""
    return guarded(f"show(requests.get({arguments}))")


def session(arguments: str, call: str) -> str:
    """One call on a Session created with the given arguments."""
    return guarded(f"with requests.Session({arguments}) as session:\n    show(session.{call})")


def async_session(arguments: str, call: str) -> str:
    """The same with AsyncSession."""
    return (ASYNC_HEADER + f"\n\nasync def main():\n    async with AsyncSession({arguments}) as session:\n        show(await session.{call})\n\n\n"
            "try:\n    asyncio.run(main())\nexcept Exception as error:\n    show_error(error)\n")


HANDLER = '''
import re

from curl_cffi import requests
from curl_cffi.requests.exceptions import ConnectionError, ProxyError
from lab_common import CA, CLOSED_HTTPS_URL, HTTP_URL, HTTPS_URL, PROXY, PROXY_AUTH, PROXY_CLOSED, SOCKS_FAILING

CONNECT_STATUS = re.compile(r"CONNECT tunnel failed, response (\\d{3})")


class ProxyFailure(Exception):
    pass


def fetch(session, url):
    try:
        response = session.get(url)
    except ProxyError as error:
        raise ProxyFailure(f"proxy error, curl {error.code}") from error
    except ConnectionError as error:
        rejected = CONNECT_STATUS.search(str(error))
        if error.code == 7 and rejected:
            raise ProxyFailure(f"proxy answered CONNECT with {rejected[1]}") from error
        raise
    if response.status_code == 407:
        raise ProxyFailure("proxy answered 407")
    return response


for label, url, proxy in [
    ("https, credentials ok", HTTPS_URL, PROXY_AUTH),
    ("https, no credentials", HTTPS_URL, PROXY),
    ("http, no credentials", HTTP_URL, PROXY),
    ("https, target port closed", CLOSED_HTTPS_URL, PROXY_AUTH),
    ("https, SOCKS failure", HTTPS_URL, SOCKS_FAILING),
    ("https, proxy port closed", HTTPS_URL, PROXY_CLOSED),
]:
    try:
        with requests.Session(impersonate="chrome", proxy=proxy, verify=CA) as session:
            print(f"HANDLER {label}: status {fetch(session, url).status_code}", flush=True)
    except Exception as error:
        print(f"HANDLER {label}: {type(error).__name__}: {error}", flush=True)
'''.lstrip("\n")

UPPER_HTTP = {"HTTP_PROXY": "http://user:pass@127.0.0.1:8888"}
LOWER_HTTP = {"http_proxy": "http://user:pass@127.0.0.1:8888"}
UPPER_HTTPS = {"HTTPS_PROXY": "http://user:pass@127.0.0.1:8888"}
LOWER_HTTPS = {"https_proxy": "http://user:pass@127.0.0.1:8888"}

CASES: list[tuple[str, dict[str, str], str]] = [
    # An https:// URL through the HTTP proxy: the proxy answers the CONNECT request.
    ("https_no_credentials", {}, get("HTTPS_URL, proxy=PROXY, verify=CA")),
    ("https_no_credentials_impersonate", {}, get('HTTPS_URL, proxy=PROXY, impersonate="chrome", verify=CA')),
    ("https_wrong_password", {}, get("HTTPS_URL, proxy=PROXY_WRONG, verify=CA")),
    ("https_no_credentials_proxies_dict", {}, get('HTTPS_URL, proxies={"https": PROXY}, verify=CA')),
    ("https_no_credentials_session", {}, session('impersonate="chrome", proxy=PROXY, verify=CA', "get(HTTPS_URL)")),
    ("https_credentials", {}, get("HTTPS_URL, proxy=PROXY_AUTH, verify=CA")),
    ("https_credentials_impersonate", {}, get('HTTPS_URL, proxy=PROXY_AUTH, impersonate="chrome", verify=CA')),
    ("https_credentials_proxies_dict", {}, get('HTTPS_URL, proxies={"http": PROXY_AUTH, "https": PROXY_AUTH}, verify=CA')),
    ("https_credentials_proxies_dict_impersonate", {}, get('HTTPS_URL, proxies={"http": PROXY_AUTH, "https": PROXY_AUTH}, impersonate="chrome", verify=CA')),
    ("https_credentials_proxy_auth", {}, get('HTTPS_URL, proxy=PROXY, proxy_auth=("user", "pass"), verify=CA')),
    ("https_credentials_session_impersonate", {}, session('impersonate="chrome", proxy=PROXY_AUTH, verify=CA', "get(HTTPS_URL)")),
    ("https_credentials_session_proxies_dict", {}, session('proxies={"http": PROXY_AUTH, "https": PROXY_AUTH}, verify=CA', "get(HTTPS_URL)")),
    ("password_percent_encoded", {}, get('HTTPS_URL, proxy=f"http://user:p%40ss%3Aw%2Frd@{PROXY_C_HOSTPORT}", verify=CA')),
    ("password_not_encoded", {}, get('HTTPS_URL, proxy=f"http://user:p@ss:w/rd@{PROXY_C_HOSTPORT}", verify=CA')),
    ("password_special_proxy_auth", {}, get('HTTPS_URL, proxy=f"http://{PROXY_C_HOSTPORT}", proxy_auth=("user", "p@ss:w/rd"), verify=CA')),
    ("proxy_and_proxies_together", {}, get('HTTPS_URL, proxy=PROXY_AUTH, proxies={"https": PROXY_AUTH}, verify=CA')),

    # Other ways an https:// request fails before it reaches the target.
    ("https_proxy_port_closed", {}, get("HTTPS_URL, proxy=PROXY_CLOSED, verify=CA")),
    ("https_target_port_closed_no_proxy", {}, get("CLOSED_HTTPS_URL, verify=CA")),
    ("https_target_port_closed_through_proxy", {}, get("CLOSED_HTTPS_URL, proxy=PROXY_AUTH, verify=CA")),
    ("https_http_scheme_for_socks_port", {}, get('HTTPS_URL, proxy="http://127.0.0.1:1080", verify=CA')),
    ("https_https_scheme_for_http_proxy", {}, get('HTTPS_URL, proxy="https://user:pass@127.0.0.1:8888", verify=CA')),
    ("proxy_host_does_not_resolve", {}, get('HTTPS_URL, proxy="http://user:pass@proxy.invalid:8080", verify=CA')),

    # An http:// URL through the HTTP proxy: the proxy's answer is the response.
    ("http_no_credentials", {}, get("HTTP_URL, proxy=PROXY")),
    ("http_no_credentials_impersonate", {}, get('HTTP_URL, proxy=PROXY, impersonate="chrome"')),
    ("http_wrong_password", {}, get("HTTP_URL, proxy=PROXY_WRONG")),
    ("http_no_credentials_raise_for_status", {}, guarded("response = requests.get(HTTP_URL, proxy=PROXY)\nshow(response)\nresponse.raise_for_status()")),
    ("http_credentials", {}, get("HTTP_URL, proxy=PROXY_AUTH")),
    ("http_credentials_impersonate", {}, get('HTTP_URL, proxy=PROXY_AUTH, impersonate="chrome"')),
    ("http_proxies_dict_https_key_only", {}, get('HTTP_URL, proxies={"https": PROXY_AUTH}')),
    ("http_proxy_port_closed", {}, get("HTTP_URL, proxy=PROXY_CLOSED")),

    # SOCKS5: who resolves the hostname, and what a failure raises.
    ("socks5_http_hostname", {}, get("PROBE_HTTP_URL, proxy=SOCKS")),
    ("socks5h_http_hostname", {}, get("PROBE_HTTP_URL, proxy=SOCKS_H")),
    ("socks5_https_hostname", {}, get("PROBE_HTTPS_URL, proxy=SOCKS, verify=CA")),
    ("socks5h_https_hostname", {}, get("PROBE_HTTPS_URL, proxy=SOCKS_H, verify=CA")),
    ("socks5_https_hostname_impersonate", {}, get('PROBE_HTTPS_URL, proxy=SOCKS, impersonate="chrome", verify=CA')),
    ("socks5h_https_hostname_impersonate", {}, get('PROBE_HTTPS_URL, proxy=SOCKS_H, impersonate="chrome", verify=CA')),
    ("socks5h_proxies_dict", {}, get('PROBE_HTTP_URL, proxies={"http": SOCKS_H, "https": SOCKS_H}')),
    ("socks5_general_failure", {}, get("HTTP_URL, proxy=SOCKS_FAILING")),
    ("socks5_general_failure_https", {}, get("HTTPS_URL, proxy=SOCKS_FAILING, verify=CA")),
    ("socks5_destination_refused", {}, get("CLOSED_HTTP_URL, proxy=SOCKS")),
    ("socks5_port_closed", {}, get("HTTP_URL, proxy=SOCKS_CLOSED")),
    ("socks5_scheme_for_http_proxy_port", {}, get('HTTP_URL, proxy="socks5://127.0.0.1:8888"')),

    # Environment variables, no proxy in the code.
    ("env_upper_http_proxy_http_url", UPPER_HTTP, get("HTTP_URL")),
    ("env_lower_http_proxy_http_url", LOWER_HTTP, get("HTTP_URL")),
    ("env_upper_http_proxy_https_url", UPPER_HTTP, get("HTTPS_URL, verify=CA")),
    ("env_lower_http_proxy_https_url", LOWER_HTTP, get("HTTPS_URL, verify=CA")),
    ("env_upper_https_proxy_https_url", UPPER_HTTPS, get("HTTPS_URL, verify=CA")),
    ("env_lower_https_proxy_https_url", LOWER_HTTPS, get("HTTPS_URL, verify=CA")),
    ("env_upper_https_proxy_http_url", UPPER_HTTPS, get("HTTP_URL")),
    ("env_upper_all_proxy_http_url", {"ALL_PROXY": "http://user:pass@127.0.0.1:8888"}, get("HTTP_URL")),
    ("env_lower_all_proxy_http_url", {"all_proxy": "http://user:pass@127.0.0.1:8888"}, get("HTTP_URL")),
    ("env_upper_all_proxy_https_url", {"ALL_PROXY": "http://user:pass@127.0.0.1:8888"}, get("HTTPS_URL, verify=CA")),
    ("env_upper_http_proxy_impersonate", UPPER_HTTP, get('HTTP_URL, impersonate="chrome"')),
    ("env_lower_http_proxy_impersonate", LOWER_HTTP, get('HTTP_URL, impersonate="chrome"')),
    ("env_lower_http_proxy_no_credentials", {"http_proxy": "http://127.0.0.1:8888"}, get("HTTP_URL")),
    ("env_upper_https_proxy_no_credentials", {"HTTPS_PROXY": "http://127.0.0.1:8888"}, get("HTTPS_URL, verify=CA")),
    ("env_lower_http_proxy_upper_no_proxy", {**LOWER_HTTP, "NO_PROXY": "127.0.0.1"}, get("HTTP_URL")),
    ("env_lower_http_proxy_lower_no_proxy", {**LOWER_HTTP, "no_proxy": "127.0.0.1"}, get("HTTP_URL")),
    ("env_no_proxy_with_proxy_argument", {"NO_PROXY": "127.0.0.1"}, get("HTTP_URL, proxy=PROXY_AUTH")),
    ("env_lower_http_proxy_proxy_argument_wins", {"http_proxy": "http://127.0.0.1:8888"}, get("HTTP_URL, proxy=PROXY_B_AUTH")),
    ("env_lower_http_proxy_trust_env_false", LOWER_HTTP, session("trust_env=False", "get(HTTP_URL)")),
    ("env_upper_https_proxy_trust_env_false", UPPER_HTTPS, session("trust_env=False, verify=CA", "get(HTTPS_URL)")),
    ("env_lower_http_proxy_proxy_empty_string", LOWER_HTTP, get('HTTP_URL, proxy=""')),
    ("env_lower_http_proxy_proxies_all_empty", LOWER_HTTP, get('HTTP_URL, proxies={"all": ""}')),
    ("env_upper_https_proxy_proxies_all_empty", UPPER_HTTPS, get('HTTPS_URL, proxies={"all": ""}, verify=CA')),
    ("env_lower_http_proxy_session_proxies_all_empty", LOWER_HTTP, session('proxies={"all": ""}', "get(HTTP_URL)")),
    ("env_lower_http_proxy_curl_options_noproxy", LOWER_HTTP,
     guarded('with requests.Session(curl_options={CurlOpt.NOPROXY: "*"}) as session:\n    show(session.get(HTTP_URL))',
             "from curl_cffi import CurlOpt, requests\nfrom lab_common import *\n")),
    ("env_lower_http_proxy_request_curl_options_noproxy", LOWER_HTTP,
     guarded('show(requests.get(HTTP_URL, curl_options={CurlOpt.NOPROXY: "*"}))', "from curl_cffi import CurlOpt, requests\nfrom lab_common import *\n")),
    ("env_upper_https_proxy_request_curl_options_noproxy", UPPER_HTTPS,
     guarded('show(requests.get(HTTPS_URL, curl_options={CurlOpt.NOPROXY: "*"}, verify=CA))', "from curl_cffi import CurlOpt, requests\nfrom lab_common import *\n")),
    ("env_no_proxy_with_proxy_argument_https", {"NO_PROXY": "127.0.0.1"}, get("HTTPS_URL, proxy=PROXY_AUTH, verify=CA")),
    ("env_no_proxy_with_proxy_argument_curl_options", {"NO_PROXY": "127.0.0.1"},
     guarded('show(requests.get(HTTP_URL, proxy=PROXY_AUTH, curl_options={CurlOpt.NOPROXY: ""}))', "from curl_cffi import CurlOpt, requests\nfrom lab_common import *\n")),

    # AsyncSession.
    ("async_https_credentials_impersonate", {}, async_session('impersonate="chrome", proxy=PROXY_AUTH, verify=CA', "get(HTTPS_URL)")),
    ("async_https_no_credentials", {}, async_session('impersonate="chrome", proxy=PROXY, verify=CA', "get(HTTPS_URL)")),
    ("async_https_request_proxy_no_credentials", {}, async_session("verify=CA", "get(HTTPS_URL, proxy=PROXY)")),
    ("async_http_no_credentials", {}, async_session("proxy=PROXY", "get(HTTP_URL)")),
    ("async_http_credentials", {}, async_session("proxy=PROXY_AUTH", "get(HTTP_URL)")),
    ("async_socks5_hostname", {}, async_session("proxy=SOCKS", "get(PROBE_HTTP_URL)")),
    ("async_socks5h_hostname", {}, async_session("proxy=SOCKS_H", "get(PROBE_HTTP_URL)")),
    ("async_socks5_general_failure", {}, async_session("proxy=SOCKS_FAILING", "get(HTTP_URL)")),
    ("async_https_no_credentials_retry_2", {}, async_session("proxy=PROXY, verify=CA, retry=2", "get(HTTPS_URL)")),
    ("async_env_upper_http_proxy_http_url", UPPER_HTTP, async_session("", "get(HTTP_URL)")),
    ("async_env_lower_http_proxy_http_url", LOWER_HTTP, async_session("", "get(HTTP_URL)")),

    # Retries.
    ("https_no_credentials_retry_2", {}, session("proxy=PROXY, verify=CA, retry=2", "get(HTTPS_URL)")),
    ("post_lost_response", {}, guarded('show(requests.post(JOBS_URL, data="job-default", proxy=PROXY_AUTH))')),
    ("post_lost_response_retry_1", {}, session("proxy=PROXY_AUTH, retry=1", 'post(JOBS_URL, data="job-retried")')),

    # The except block from the guide.
    ("handler", {}, HANDLER),
]
