# 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 unittest.mock import ANY

import pytest
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent
from typing import NamedTuple

from czc.constants import CLOUDZERO_TEST_KEY_DO_NOT_FORWARD_SUFFIX, PROJECT_NAME
from czt.unittest import MATCH, Context, MockEx

from czc.domain_events import EventMetadata
from czc.type_util import deserializable
from tests.sample_data import (NAMESPACE, get_test_event,
                               get_test_event_api_key_context)
from tests.sample_data.sample_data import ORGANIZATION_ID, get_event_bridge_test_event, EVENT_ID, CLOUD_ACCOUNT_ID, \
    CLOUD_REGION

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
