"""Index credentials stay on their exact HTTPS origin, including redirects."""
from __future__ import annotations

from email.message import Message
import io
import urllib.request
import urllib.response

import pytest

from pm import update
from pm import packages


class IndexTransport(urllib.request.HTTPHandler, urllib.request.HTTPSHandler):
    def __init__(self, redirects=None, body=b'{}'):
        super().__init__()
        self.redirects = redirects or {}
        self.body = body
        self.sent = []

    def http_open(self, request):
        self.sent.append((request.full_url, dict(request.header_items())))
        headers = Message()
        destination = self.redirects.get(request.full_url)
        if destination:
            headers["Location"] = destination
        response = urllib.response.addinfourl(
            io.BytesIO(self.body), headers, request.full_url, 302 if destination else 200,
        )
        response.msg = "Found" if destination else "OK"
        return response

    https_open = http_open


@pytest.mark.parametrize("url,token", [
    ("https://api.github.com/repos/example/tool/releases", "dummy-gh"),
    ("https://api.github.com:443/repos/example/tool/releases", "dummy-gh"),
    ("https://huggingface.co/api/buckets/example/tool/tree", "dummy-hf"),
    ("https://nodejs.org/dist/index.json", None),
    ("https://registry.npmjs.org/npm", None),
    ("https://evilhuggingface.co/index", None),
    ("https://api.github.com.evil.invalid/index", None),
    ("https://api.github.com:444/index", None),
    ("http://api.github.com/index", None),
])
def test_index_credentials_match_the_exact_origin(monkeypatch, url, token):
    transport = IndexTransport()
    monkeypatch.setattr(urllib.request, "_opener", urllib.request.build_opener(transport))
    monkeypatch.setenv("GH_TOKEN", "dummy-gh")
    monkeypatch.setenv("HF_TOKEN", "dummy-hf")

    assert update._get_json(url) == {}

    headers = {key.lower(): value for key, value in transport.sent[-1][1].items()}
    assert headers.get("authorization") == (f"Bearer {token}" if token else None)
    assert headers["user-agent"] == "hermes-pm"


@pytest.mark.parametrize("reader", ["json", "text", "release-digest"])
@pytest.mark.parametrize("destination,credential_survives", [
    ("https://api.github.com/second", True),
    ("https://unrelated.invalid/second", False),
    ("https://api.github.com:444/second", False),
    ("http://api.github.com/second", False),
])
def test_index_redirects_use_the_credential_safe_opener(monkeypatch, reader, destination, credential_survives):
    origin = "https://api.github.com/repos/ggml-org/llama.cpp/releases/tags/b123"
    transport = IndexTransport(
        {origin: destination},
        body=b'{"assets": [{"name": "tool.zip", "digest": "sha256:abc123"}]}',
    )
    monkeypatch.setattr(urllib.request, "_opener", urllib.request.build_opener(transport))
    monkeypatch.setenv("GH_TOKEN", "dummy-gh")

    if reader == "release-digest":
        monkeypatch.setattr(packages, "_release_digest_cache", {})
        package = packages.LlamaCpp()
        assert package.known_sha256("123", "https://github.com/example/tool.zip") == "abc123"
        # A cache hit must retain the parsed result without making another request.
        assert package.known_sha256("123", "https://github.com/example/tool.zip") == "abc123"
    else:
        {"json": update._get_json, "text": update._get_text}[reader](origin)

    assert [url for url, _ in transport.sent] == [origin, destination]
    initial, redirected = [{key.lower(): value for key, value in headers.items()} for _, headers in transport.sent]
    assert initial["authorization"] == "Bearer dummy-gh"
    assert (redirected.get("authorization") == "Bearer dummy-gh") is credential_survives
