"""Unit tests for the deploy stream helpers (server/deploy_stream.py)."""

import json

import pytest
from pydantic import ValidationError

from server.deploy_stream import DeployStreamRequest, make_event, redact_event, to_jsonl_line


class DescribeDeployStreamRequest:

    def test_valid_request(self):
        # given a well-formed request body
        data = {
            "deployment": {
                "deploymentId": "my-deploy",
                "resources": [],
            }
        }

        # when we validate it
        req = DeployStreamRequest.model_validate(data)

        # then the deployment fields are parsed correctly
        assert req.deployment.deployment_id == "my-deploy"
        assert req.deployment.resources == []

    def test_missing_deployment_field(self):
        # given a request body with no deployment field
        # when we validate it
        # then a ValidationError is raised
        with pytest.raises(ValidationError):
            DeployStreamRequest.model_validate({})

    def test_missing_deployment_id(self):
        # given a deployment payload with no deploymentId
        # when we validate it
        # then a ValidationError is raised
        with pytest.raises(ValidationError):
            DeployStreamRequest.model_validate({
                "deployment": {"resources": []},
            })

    def test_missing_resources(self):
        # given a deployment payload with no resources field
        # when we validate it
        # then a ValidationError is raised
        with pytest.raises(ValidationError):
            DeployStreamRequest.model_validate({
                "deployment": {"deploymentId": "x"},
            })


class DescribeMakeEvent:

    def test_has_required_fields(self):
        # given a set of event parameters
        # when we build an event envelope
        event = make_event(
            deployment_id="dep-1",
            request_id="req-1",
            event_type="deployment_status",
            status="pending",
            sequence=1,
        )

        # then all required fields are present with correct values
        assert event["deploymentId"] == "dep-1"
        assert event["requestId"] == "req-1"
        assert event["eventType"] == "deployment_status"
        assert event["status"] == "pending"
        assert event["sequence"] == 1
        assert event["data"] == {}
        assert "T" in event["ts"]


class DescribeToJsonlLine:

    def test_ends_with_newline(self):
        # given an event dict
        # when we serialise it to JSONL
        # then the line ends with a newline
        assert to_jsonl_line({"a": 1}).endswith("\n")

    def test_is_valid_json(self):
        # given an event dict
        # when we serialise and parse it back
        parsed = json.loads(to_jsonl_line({"key": "value"}))

        # then the values round-trip correctly
        assert parsed["key"] == "value"


class DescribeRedactEvent:

    def test_redacts_known_secret_in_string_field(self):
        # given an event containing a known secret value
        secret = "my-secret-token-abc123"
        event = {"data": {"message": f"Bearer {secret}"}}

        # when we redact the event with that secret
        result = redact_event(event, {secret})

        # then the secret is replaced and no longer present
        assert secret not in json.dumps(result)
        assert "********" in result["data"]["message"]

    def test_leaves_non_secret_strings_alone(self):
        # given an event with no secrets
        event = {"data": {"message": "Deployment complete", "count": 42}}

        # when we redact it with an unrelated secret
        # then nothing changes
        assert redact_event(event, {"not-in-the-event"}) == event

    def test_redacts_multiple_secrets(self):
        # given an event containing two different secrets
        token = "bearer-token-xyz"
        vso = "vso-secret-abc"
        event = {"data": {"message": f"token={token} vso={vso}"}}

        # when we redact both
        result = redact_event(event, {token, vso})

        # then neither appears in the output
        assert token not in json.dumps(result)
        assert vso not in json.dumps(result)
