# Copyright (c) 2022 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

from dataclasses import dataclass
from typing import Any
from uuid import uuid4

import pyfaaster.aws.tools as faaster_aws_tools
from czc.api.v2.constants import (CLOUDZERO_IDEMPOTENCY_KEY_HEADER,
                                  CURSOR_PARAMETER)
from czc.constants import FEATURE_NAME

from czt.smoke.api_test_runner import PublicV2ApiKeyTestRunner

logger = faaster_aws_tools.setup_logging(FEATURE_NAME)


@dataclass(frozen=True)
class PublicV2ApiResult(object):
    raw_result: dict

    @property
    def status_code(self):
        return self.raw_result['statusCode']

    @property
    def body(self):
        return self.raw_result['body']


@dataclass(frozen=True)
class PublicV2ApiObjectResult(PublicV2ApiResult):
    object_type_name: str

    @property
    def id(self):
        return self.object['id']

    @property
    def object(self):
        if not(self.status_code == 200 or self.status_code == 201):
            raise Exception(f'Operation failed with status = {self.status_code}.')

        return self.body[self.object_type_name]


@dataclass(frozen=True)
class PublicV2ApiPageResult(PublicV2ApiResult):
    object_type_name_plural: str

    @property
    def items(self):
        return self.body[self.object_type_name_plural]

    @property
    def next_cursor(self):
        return self.body['pagination'][CURSOR_PARAMETER]['next_cursor']

    @property
    def prev_cursor(self):
        return self.body['pagination'][CURSOR_PARAMETER]['prev_cursor']

    @property
    def page_count(self):
        return self.body['pagination']['page_count']

    @property
    def items_in_page(self):
        return self.body['pagination']['item_count']

    @property
    def total_items(self):
        return self.body['pagination']['total_count']


class PublicV2ApiTestClient(object):
    _namespace: str
    _org_id: str
    _configuration: Any
    _public_url: str
    _use_test_key: bool
    _cz_test_key: str
    _object_type_name: str
    _object_type_name_plural: str
    _operations_endpoints: dict
    _state: Any

    @property
    def namespace(self):
        return self._namespace

    @property
    def state(self):
        return self._state

    @property
    def org_id(self):
        return self._org_id

    @property
    def cz_test_key(self):
        return self._cz_test_key if self._use_test_key else None

    def __init__(self, namespace, org_id, configuration, public_url, object_type_name, object_type_name_plural=None, operation_endpoints=None, use_test_key=True, cz_test_key=None) -> None:
        super().__init__()
        self._namespace = namespace
        self._org_id = org_id
        self._configuration = configuration
        self._public_url = public_url
        self._object_type_name = object_type_name
        self._object_type_name_plural = object_type_name_plural or f'{object_type_name}s'
        self._use_test_key = use_test_key
        self._cz_test_key = (cz_test_key or f'smoketest-{uuid4()}') if use_test_key else ''
        self._operations_endpoints = {
            'create': f'api-{self._object_type_name_plural}:create-{self._object_type_name}',
            'update': f'api-{self._object_type_name_plural}:update-{self._object_type_name}',
            'get': f'api-{self._object_type_name_plural}:get-{self._object_type_name}',
            'get-all': f'api-{self._object_type_name_plural}:get-all-{self._object_type_name_plural}',
            'delete': f'api-{self._object_type_name_plural}:delete-{self._object_type_name}',
            **(operation_endpoints or {})
        }

    def create(self, request, additional_path_parameters=None, idempotent_key=None, expected_result=None):
        test = {
            'endpoint': self._operations_endpoints['create'],
            **({'headers': {CLOUDZERO_IDEMPOTENCY_KEY_HEADER: idempotent_key}} if idempotent_key else {}),
            'json': request,
            **({'pathParameters': additional_path_parameters} if additional_path_parameters else {}),
            'result': {
                'statusCode': 200,
                **(expected_result or {})
            }
        }
        runner = PublicV2ApiKeyTestRunner(self._namespace, self._org_id, self._configuration, test, self._public_url, self, self._use_test_key, self._cz_test_key)
        return PublicV2ApiObjectResult(runner.run(), self._object_type_name)

    def update(self, id, request, additional_path_parameters=None, idempotent_key=None, expected_result=None):
        test = {
            'endpoint': self._operations_endpoints['update'],
            **({'headers': {CLOUDZERO_IDEMPOTENCY_KEY_HEADER: idempotent_key}} if idempotent_key else {}),
            'json': request,
            'pathParameters': {
                f'{self._object_type_name}_id': id,
                **(additional_path_parameters or {})
            },
            'result': {
                'statusCode': 200,
                **(expected_result or {})
            }
        }
        runner = PublicV2ApiKeyTestRunner(self._namespace, self._org_id, self._configuration, test, self._public_url, self, self._use_test_key, self._cz_test_key)
        return PublicV2ApiObjectResult(runner.run(), self._object_type_name)

    def get(self, id, query_parameters=None, additional_path_parameters=None, expected_result=None):
        test = {
            'endpoint': self._operations_endpoints['get'],
            'pathParameters': {
                f'{self._object_type_name}_id': id,
                **(additional_path_parameters or {})
            },
            **({'params': query_parameters} if query_parameters else {}),
            'result': {
                'statusCode': 200,
                **(expected_result or {})
            }
        }
        runner = PublicV2ApiKeyTestRunner(self._namespace, self._org_id, self._configuration, test, self._public_url, self, self._use_test_key, self._cz_test_key)
        return PublicV2ApiObjectResult(runner.run(), self._object_type_name)

    def get_all(self, query_parameters=None, additional_path_parameters=None, expected_result=None):
        test = {
            'endpoint': self._operations_endpoints['get-all'],
            **({'pathParameters': additional_path_parameters} if additional_path_parameters else {}),
            **({'params': query_parameters} if query_parameters else {}),
            'result': {
                'statusCode': 200,
                **(expected_result or {})
            }
        }
        runner = PublicV2ApiKeyTestRunner(self._namespace, self._org_id, self._configuration, test, self._public_url, self, self._use_test_key, self._cz_test_key)
        return PublicV2ApiPageResult(runner.run(), self._object_type_name_plural)

    def delete(self, id, additional_path_parameters=None, idempotent_key=None, expected_result=None):
        test = {
            'endpoint': self._operations_endpoints['delete'],
            **({'headers': {CLOUDZERO_IDEMPOTENCY_KEY_HEADER: idempotent_key}} if idempotent_key else {}),
            'pathParameters': {
                f'{self._object_type_name}_id': id,
                **(additional_path_parameters or {})
            },
            'result': {
                'statusCode': 200,
                **(expected_result or {})
            }
        }
        runner = PublicV2ApiKeyTestRunner(self._namespace, self._org_id, self._configuration, test, self._public_url, self, self._use_test_key, self._cz_test_key)
        return PublicV2ApiResult(runner.run())
