# Copyright (c) 2021 CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com
import os
from datetime import datetime
from typing import NamedTuple
from unittest.mock import ANY

import pytest
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent

from czc.constants import CLOUDZERO_TEST_KEY_DO_NOT_FORWARD_SUFFIX, PROJECT_NAME
from czc.domain_events import EventMetadata
from czc.type_util import deserializable
from czt.unittest import MATCH, Context, MockEx

from tests.sample_data import NAMESPACE, get_test_event, get_test_event_api_key_context
from tests.sample_data.sample_data import CLOUD_ACCOUNT_ID, CLOUD_REGION, EVENT_ID, ORGANIZATION_ID, get_event_bridge_test_event

TEST_KEY_VALUES = [None, 'test-key-value', f'test-key-value{CLOUDZERO_TEST_KEY_DO_NOT_FORWARD_SUFFIX}']


@pytest.fixture(scope='function')
def context(mocker, environ):
    from czc import domain_events

    with Context(mocker, domain_events, ['event_bridge'], extra={'domain_events': domain_events}) as context:
        yield context


@pytest.mark.unit
@pytest.mark.parametrize(
    'event',
    [
        pytest.param(get_test_event(), id='Bearer Token Event Dictionary'),
        pytest.param(get_test_event_api_key_context(), id='API Key Event Dictionary'),
        pytest.param(APIGatewayProxyEvent(get_test_event()), id='Bearer Token Event APIGatewayProxyEvent'),
        pytest.param(APIGatewayProxyEvent(get_test_event_api_key_context()), id='API Key Event APIGatewayProxyEvent'),
    ],
)
def test_publish_event_response(context, lambda_context, event):
    detail_type = 'detail-type'
    event_name = 'event-name'
    response = {'key': 'value'}

    @context.domain_events.publish_event_response(detail_type, event_name, response_filter_fn=lambda x: x)
    def handler(_, __):
        return response

    handler(event, lambda_context)
    assert MockEx(context.mock_event_bridge.publish_event).was_called_once_with(
        NAMESPACE,
        detail_type,
        PROJECT_NAME,
        MATCH(
            lambda x: (
                x['cz_organization_id'] == ORGANIZATION_ID
                and x['event_name'] == event_name
                and 'email' not in x['data']['event']['requestContext']['authorizer']
                and 'headers' in x['data']['event']
                and x['data']['response'] == response
            )
        ),
    )


@deserializable()
class SomeEvent(NamedTuple):
    foo: str


@deserializable()
class OtherEvent(NamedTuple):
    a: int
    b: int
    c: str


@pytest.mark.unit
@pytest.mark.parametrize('bad_event', [{'some': 'event'}, (1, 2, 3), 'publish me'])
def test_publish_should_raise_exception_for_bad_types(context, bad_event):
    with pytest.raises(ValueError):
        context.domain_events.publish(bad_event)


@pytest.mark.unit
@pytest.mark.parametrize(
    'namespace,event,expected_detail_type,expected_detail',
    [
        ('alfa', SomeEvent(foo='bar'), 'SomeEvent', {'foo': 'bar'}),
        ('live', OtherEvent(a=1, b=2, c='x'), 'OtherEvent', {'a': 1, 'b': 2, 'c': 'x'}),
    ],
)
def test_publish_should_send_event_with_expected_args(context, namespace, event, expected_detail_type, expected_detail):
    os.environ['NAMESPACE'] = namespace
    context.domain_events.publish(event)
    context.mock_event_bridge.publish_event.assert_called_once_with(namespace, expected_detail_type, 'cz-czc', expected_detail)


@pytest.mark.unit
def test_publish_event_should_be_no_op_if_running_as_smoke_test(context):
    os.environ['cloudzero_test_key'] = 'foo'
    context.domain_events.publish(SomeEvent(foo='bar'))
    assert not context.mock_event_bridge.publish_event.called


@pytest.mark.unit
@pytest.mark.parametrize(
    'event_detail,event_type,expected_event',
    [
        ({'foo': 'some data'}, SomeEvent, SomeEvent(foo='some data')),
        ({'a': 999, 'b': 888, 'c': 'some numbers'}, OtherEvent, OtherEvent(a=999, b=888, c='some numbers')),
    ],
)
def test_event_consumer_should_call_handler_with_event_object(
    context, lambda_context, event_type, event_detail, expected_event
):
    @context.domain_events.event_consumer(event_type=event_type)
    def handler(event):
        return event

    test_event = get_event_bridge_test_event(event_detail)
    result = handler(test_event, lambda_context)

    assert result == expected_event


@pytest.mark.unit
@pytest.mark.parametrize(
    'time,expected_metadata',
    [
        (
            datetime(2023, 3, 12),
            EventMetadata(
                dict(
                    version='0',
                    id=EVENT_ID,
                    source='cz-czc',
                    account=CLOUD_ACCOUNT_ID,
                    time=datetime(2023, 3, 12).isoformat(),
                    region=CLOUD_REGION,
                    detail=ANY,
                )
            ),
        )
    ],
)
def test_event_consumer_should_call_handler_with_event_metadata(context, lambda_context, time, expected_metadata):
    @context.domain_events.event_consumer(event_type=SomeEvent, include_metadata=True)
    def handler(event: SomeEvent, metadata: EventMetadata):
        return metadata

    test_event = get_event_bridge_test_event({'foo': 'bar'}, time)
    result = handler(test_event, lambda_context)

    assert result.timestamp == time
    assert result == expected_metadata
