import json

import pytest

from sdk_test_support.mock_server import (
    AUTHORIZATION_HEADER,
    ERROR_STATUS_HEADER,
    OPERATION_ID_HEADER,
    MockApiServer,
)
from sdk_test_support.mock_server_types import ErrorResponseExpectation, OperationExpectation


@pytest.fixture
def manifest():
    return {
        "createAlertRule": OperationExpectation(
            operation_id="createAlertRule",
            method="POST",
            path="/alerts/rules",
            request_body_example={"ruleName": "Example"},
            success_status=201,
            success_body={"ruleId": "1"},
            error_responses={
                "400": ErrorResponseExpectation(
                    status=400,
                    body={"title": "Bad Request", "status": 400},
                )
            },
        ),
        "deleteAlertRule": OperationExpectation(
            operation_id="deleteAlertRule",
            method="DELETE",
            path="/alerts/rules/{ruleId}",
            path_param_examples={"ruleId": "127094"},
            success_status=204,
            success_body=None,
        ),
        "getAlertRule": OperationExpectation(
            operation_id="getAlertRule",
            method="GET",
            path="/alerts/rules/{ruleId}",
            path_param_examples={"ruleId": "127094"},
            success_status=200,
            success_body={"ruleId": "127094", "ruleName": "Example"},
        ),
    }


def _request(server: MockApiServer, *, method: str, path: str, headers=None, body=None):
    import urllib.request

    request_headers = {
        AUTHORIZATION_HEADER: "Bearer test-token",
        OPERATION_ID_HEADER: "createAlertRule",
    }
    if headers:
        request_headers.update(headers)

    data = None
    if body is not None:
        data = json.dumps(body).encode("utf-8")
        request_headers["Content-Type"] = "application/json"

    request = urllib.request.Request(
        server.base_url + path,
        data=data,
        headers=request_headers,
        method=method,
    )
    try:
        with urllib.request.urlopen(request) as response:
            return response.status, response.read()
    except urllib.error.HTTPError as exc:
        return exc.code, exc.read()


def test_mock_server_happy_path(manifest):
    with MockApiServer(manifest) as server:
        status, body = _request(
            server,
            method="POST",
            path="/alerts/rules",
            body={"ruleName": "Example"},
        )
        assert status == 201
        assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}


def test_mock_server_rejects_missing_authorization(manifest):
    with MockApiServer(manifest) as server:
        status, _ = _request(
            server,
            method="POST",
            path="/alerts/rules",
            headers={AUTHORIZATION_HEADER: ""},
            body={"ruleName": "Example"},
        )
        assert status == 401


def test_mock_server_rejects_invalid_request_body(manifest):
    with MockApiServer(manifest) as server:
        status, _ = _request(
            server,
            method="POST",
            path="/alerts/rules",
            body={"ruleName": "Wrong"},
        )
        assert status == 400


def test_mock_server_error_path(manifest):
    with MockApiServer(manifest) as server:
        status, body = _request(
            server,
            method="POST",
            path="/alerts/rules",
            headers={ERROR_STATUS_HEADER: "400"},
            body={"unexpected": True},
        )
        assert status == 400
        assert json.loads(body.decode("utf-8"))["title"] == "Bad Request"


def test_mock_server_ignores_readonly_fields_in_expected_body(manifest):
    readonly_manifest = {
        **manifest,
        "createAlertRule": OperationExpectation(
            operation_id="createAlertRule",
            method="POST",
            path="/alerts/rules",
            request_body_example={"ruleName": "Example", "ruleId": "read-only"},
            success_status=201,
            success_body={"ruleId": "1"},
        ),
    }
    with MockApiServer(readonly_manifest) as server:
        status, body = _request(
            server,
            method="POST",
            path="/alerts/rules",
            body={"ruleName": "Example"},
        )
        assert status == 201
        assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}


def test_mock_server_no_content_response(manifest):
    with MockApiServer(manifest) as server:
        import urllib.request

        request = urllib.request.Request(
            server.base_url + "/alerts/rules/127094",
            headers={
                AUTHORIZATION_HEADER: "Bearer test-token",
                OPERATION_ID_HEADER: "deleteAlertRule",
            },
            method="DELETE",
        )
        with urllib.request.urlopen(request) as response:
            assert response.status == 204
            assert response.read() == b""


def test_mock_server_matches_path_variable(manifest):
    with MockApiServer(manifest) as server:
        status, body = _request(
            server,
            method="GET",
            path="/alerts/rules/127094",
            headers={OPERATION_ID_HEADER: "getAlertRule"},
        )
        assert status == 200
        assert json.loads(body.decode("utf-8")) == {"ruleId": "127094", "ruleName": "Example"}


def test_mock_server_rejects_path_missing_path_variable(manifest):
    with MockApiServer(manifest) as server:
        status, body = _request(
            server,
            method="GET",
            path="/alerts/rules",
            headers={OPERATION_ID_HEADER: "getAlertRule"},
        )
        assert status == 400
        assert json.loads(body.decode("utf-8"))["detail"] == "Path does not match operation expectation"


def test_mock_server_accepts_equivalent_iso8601_datetime_formats(manifest):
    datetime_manifest = {
        **manifest,
        "createAlertRule": OperationExpectation(
            operation_id="createAlertRule",
            method="POST",
            path="/alerts/rules",
            request_body_example={
                "ruleName": "Example",
                "startDate": "2017-07-01T05:00:00Z",
            },
            success_status=201,
            success_body={"ruleId": "1"},
        ),
    }
    with MockApiServer(datetime_manifest) as server:
        status, body = _request(
            server,
            method="POST",
            path="/alerts/rules",
            body={
                "ruleName": "Example",
                "startDate": "2017-07-01T05:00:00+00:00",
            },
        )
        assert status == 201
        assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}


def test_mock_server_ignores_readonly_fields_in_nested_request_objects(manifest):
    nested_manifest = {
        **manifest,
        "createAlertRule": OperationExpectation(
            operation_id="createAlertRule",
            method="POST",
            path="/alerts/rules",
            request_body_example={
                "ruleName": "Example",
                "widgets": [
                    {
                        "title": "Widget Title",
                        "id": "read-only-id",
                        "embedUrl": "https://example.com/embed",
                    }
                ],
            },
            success_status=201,
            success_body={"ruleId": "1"},
        ),
    }
    with MockApiServer(nested_manifest) as server:
        status, body = _request(
            server,
            method="POST",
            path="/alerts/rules",
            body={
                "ruleName": "Example",
                "widgets": [{"title": "Widget Title"}],
            },
        )
        assert status == 201
        assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}
