# Copyright (c) 2023 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 functools import wraps
from datetime import datetime
from dateutil import parser
from enum import Enum
from toolz import keyfilter
from typing import NamedTuple, Optional, Callable, TypeVar
from voluptuous import Coerce, Maybe, Required, Schema, REMOVE_EXTRA, Optional as OptionalParam, Range, Number, \
    Datetime, All, PREVENT_EXTRA, message
from toolz.functoolz import curry

from czc.type_util import deserializable, isoparse, serializable

T = TypeVar('T')


class CloudProvider(str, Enum):
    datadog = 'datadog'
    newrelic = 'newrelic'

    @property
    def name(self):
        return {
            CloudProvider.datadog: 'Datadog',
            CloudProvider.newrelic: 'New Relic'}.get(self)


@deserializable(schema=Schema({
    Required('connection_id'): str,
    Required('organization_id'): str,
    Required('name'): str,
    Required('cloud_provider'): Coerce(CloudProvider),
    Required('created'): Maybe(isoparse),
    Required('last_updated'): Maybe(isoparse),
    'provider_config': dict,
    'is_enabled': bool,
    'is_deleted': bool,
    'capabilities': dict,
    'billing_account_id': Maybe(str)
}, extra=REMOVE_EXTRA))
class AnyCostConnectionInfo(NamedTuple):
    connection_id: str
    organization_id: str
    name: str
    cloud_provider: CloudProvider
    created: datetime
    last_updated: datetime
    provider_config: dict = {}
    is_enabled: bool = True
    is_deleted: bool = False
    capabilities: dict = {}
    billing_account_id: Optional[str] = None

    @staticmethod
    def _is_public_field(field_name: str) -> bool:
        return field_name not in {'is_deleted'}

    def to_public(self) -> dict:
        return keyfilter(self._is_public_field, serializable(self))


@curry
def custom_validator(msg: str, predicate: Callable[[T], bool]) -> Callable[[T], T]:
    @message(msg)
    @wraps(predicate)
    def _validate(obj: T) -> T:
        try:
            if predicate(obj):
                return obj
        except (TypeError, ValueError, KeyError):
            pass
        raise ValueError

    return _validate()


@custom_validator('Tier rates must specify a tier with a threshold of zero.')
def contains_zero_tier(tiers: list[dict]) -> bool:
    return any(tier['threshold'] == 0 for tier in tiers)


@custom_validator('Tier rates must have only one occurrence of each threshold.')
def contains_unique_tiers(tiers: list[dict]) -> bool:
    return len({t['threshold'] for t in tiers}) == len(tiers)


TIERED_RATE = Schema(
    {
        Required('threshold'): All(Coerce(int), Range(min=0, min_included=True)),
        Required('rate'): Number(yield_decimal=True)
    }, extra=PREVENT_EXTRA
)

TIERED_RATE_LIST = All([TIERED_RATE], contains_zero_tier, contains_unique_tiers)

NEW_RELIC_RATE_INFORMATION = Schema(
    {
        Required('cost_per_gb'): TIERED_RATE_LIST,
        Required('cost_per_basic_user'): TIERED_RATE_LIST,
        Required('cost_per_core_user'): TIERED_RATE_LIST,
        Required('cost_per_full_platform_user'): TIERED_RATE_LIST,
        Required('cost_per_synthetic_check'): TIERED_RATE_LIST,
        OptionalParam(
            'subscription_term_start_date', default='0001-01-01T00:00:01.000Z'
        ): Datetime(),
    },
    extra=PREVENT_EXTRA,
)


@deserializable(schema=TIERED_RATE)
class TieredRate(NamedTuple):
    threshold: int
    rate: float


@deserializable(schema=NEW_RELIC_RATE_INFORMATION)
class NewRelicRateInformation(NamedTuple):
    cost_per_gb: list[TieredRate]
    cost_per_basic_user: list[TieredRate]
    cost_per_core_user: list[TieredRate]
    cost_per_full_platform_user: list[TieredRate]
    cost_per_synthetic_check: list[TieredRate]
    subscription_term_start_date: str

    @property
    def term_start_datetime(self) -> datetime:
        return parser.isoparse(self.subscription_term_start_date)


class DataDogRateInformation(NamedTuple):
    pass
