import itertools
from typing import Any, Optional, Sequence

import grpc
from grpc.aio import ClientInterceptor

from .constants import (
    INVEST_GRPC_API,
    KEEPALIVE_MAX_PINGS,
    KEEPALIVE_TIME_MS,
    KEEPALIVE_TIMEOUT_MS,
    MAX_RECEIVE_MESSAGE_LENGTH,
)
from .typedefs import ChannelArgumentType

__all__ = ("create_channel",)

_required_options: ChannelArgumentType = [
    ("grpc.max_receive_message_length", MAX_RECEIVE_MESSAGE_LENGTH),
    ("grpc.keepalive_time_ms", KEEPALIVE_TIME_MS),
    ("grpc.keepalive_timeout_ms", KEEPALIVE_TIMEOUT_MS),
    ("grpc.http2.max_pings_without_data", KEEPALIVE_MAX_PINGS),
]


def create_channel(
    *,
    target: Optional[str] = None,
    options: Optional[ChannelArgumentType] = None,
    force_async: bool = False,
    compression: Optional[grpc.Compression] = None,
    interceptors: Optional[Sequence[ClientInterceptor]] = None,
) -> Any:
    creds = grpc.ssl_channel_credentials()
    target = target or INVEST_GRPC_API
    if options is None:
        options = []

    options = _with_options(options, _required_options)

    args = (target, creds, options, compression)
    if force_async:
        return grpc.aio.secure_channel(*args, interceptors=interceptors)
    return grpc.secure_channel(*args)


def _with_options(options: ChannelArgumentType, _required_options: ChannelArgumentType):
    for option in _required_options:
        options = _with_option(options, option[0], option[1])
    return options


def _with_option(
    options: ChannelArgumentType, key: str, value: Any
) -> ChannelArgumentType:
    if not _contains_option(options, key):
        option = (key, value)
        return list(itertools.chain(options, [option]))
    return options


def _contains_option(options: ChannelArgumentType, expected_option_name: str) -> bool:
    for option_name, _ in options:
        if option_name == expected_option_name:
            return True
    return False
