"""drone — SCM-configured functional test (phase drone).

Proves that drone is wired to the per-run gitea dep, not just healthy.
The negative control: a drone deployed WITHOUT DRONE_GITEA_CLIENT_ID + DRONE_GITEA_SERVER
(i.e., without compose.gitea.yml) would NOT redirect /login to the gitea dep's OAuth
authorize endpoint — it would error or redirect elsewhere. This test is therefore falsified
by a misconfigured drone.

Test: GET https://<drone>/login must issue a 303 redirect whose Location header points to
the per-run gitea dep's /login/oauth/authorize URL. We capture ONLY drone's first redirect
(not gitea's subsequent redirect to /user/login for unauthenticated users).

Per ADV-drone-01: following all redirects causes the assertion to land on gitea's /user/login
(200 OK after gitea redirects unauthenticated users away from /login/oauth/authorize), which
means the path assertion always fails. The fix is a no-follow handler that captures the
Location header from drone's 303 directly.
"""

from __future__ import annotations

import ssl
import urllib.error
import urllib.parse
import urllib.request

import pytest


class _CaptureOneRedirect(urllib.request.HTTPRedirectHandler):
    """Stop redirect-following after the FIRST hop; raise HTTPError so the caller can inspect
    the Location header from drone's 303 without following gitea's subsequent redirects."""

    def http_error_302(self, req, fp, code, msg, headers):
        raise urllib.error.HTTPError(req.full_url, code, msg, headers, fp)

    http_error_303 = http_error_302


@pytest.mark.requires_deps
def test_login_redirects_to_gitea_dep(live_app, deps):
    """Drone's /login must issue a 303 redirect to the per-run gitea dep's OAuth2 authorize
    endpoint.

    Proves: (a) gitea is the SCM backend (not github or unconfigured); (b) the OAuth2
    client_id in the Location header matches the app the harness created in the dep gitea
    instance; (c) the redirect targets the TEST-RUN gitea, not any hardcoded external provider.

    ADV-drone-01 fix: only drone's first 303 is captured; gitea's own redirects (unauthenticated
    user → /user/login) are not followed, so the path assertion is against the correct URL.
    """
    assert "gitea" in deps, (
        f"gitea dep not in deps — dep provisioning should have populated this. "
        f"Got keys: {list(deps.keys())}"
    )
    gitea = deps["gitea"]
    gitea_domain: str = gitea["domain"]
    expected_client_id: str = gitea["client_id"]

    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE

    opener = urllib.request.build_opener(
        _CaptureOneRedirect(),
        urllib.request.HTTPSHandler(context=ctx),
    )

    redirect_url = None
    try:
        opener.open(f"https://{live_app}/login", timeout=30)
        pytest.fail(
            f"Expected a 302/303 redirect from https://{live_app}/login but got 200 OK — "
            f"drone may not have gitea SCM configured (check COMPOSE_FILE + GITEA_DOMAIN)"
        )
    except urllib.error.HTTPError as e:
        if e.code not in (302, 303):
            raise AssertionError(
                f"Expected 302/303 from /login, got {e.code} — "
                f"drone may not have gitea SCM configured"
            ) from e
        redirect_url = e.headers.get("Location") or e.headers.get("location", "")

    assert redirect_url, (
        "Drone /login returned a redirect but Location header is empty — "
        "check drone gitea SCM configuration"
    )

    parsed = urllib.parse.urlparse(redirect_url)
    assert parsed.scheme == "https", f"Redirect Location has unexpected scheme: {redirect_url!r}"
    assert parsed.netloc == gitea_domain, (
        f"Drone /login did not redirect to the gitea dep ({gitea_domain!r}); "
        f"Location: {redirect_url!r} — check GITEA_DOMAIN + COMPOSE_FILE in drone's .env"
    )
    assert parsed.path == "/login/oauth/authorize", (
        f"Redirect path is {parsed.path!r}, expected /login/oauth/authorize — "
        f"drone may not have gitea SCM configured"
    )
    params = urllib.parse.parse_qs(parsed.query)
    actual_client_id = params.get("client_id", [None])[0]
    assert actual_client_id == expected_client_id, (
        f"OAuth2 client_id mismatch: drone is using {actual_client_id!r} but the harness "
        f"created app {expected_client_id!r} in the dep gitea — check install_steps.sh"
    )
