"""
Connection module for Amazon SNS

:configuration: This module accepts explicit sns credentials but can also
    utilize IAM roles assigned to the instance through Instance Profiles. Dynamic
    credentials are then automatically obtained from AWS API and no further
    configuration is necessary. More Information available at:

    .. code-block:: text

        http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html

    If IAM roles are not used you need to specify them either in a pillar or
    in the minion's config file:

    .. code-block:: yaml

        sns.keyid: GKTADJGHEIQSXMKKRBJ08H
        sns.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs

    A region may also be specified in the configuration:

    .. code-block:: yaml

        sns.region: us-east-1

    If a region is not specified, the default is us-east-1.

    It's also possible to specify key, keyid and region via a profile, either
    as a passed in dict, or as a string to pull from pillars or minion config:

    .. code-block:: yaml

        myprofile:
            keyid: GKTADJGHEIQSXMKKRBJ08H
            key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
            region: us-east-1

:depends: boto
"""

# keep lint from choking on _get_conn and _cache_id
# pylint: disable=E0602


import logging

import salt.utils.versions

log = logging.getLogger(__name__)

try:
    # pylint: disable=unused-import
    import boto
    import boto.sns

    # pylint: enable=unused-import
    logging.getLogger("boto").setLevel(logging.CRITICAL)
    HAS_BOTO = True
except ImportError:
    HAS_BOTO = False


def __virtual__():
    """
    Only load if boto libraries exist.
    """
    has_boto_reqs = salt.utils.versions.check_boto_reqs(check_boto3=False)
    if has_boto_reqs is True:
        __utils__["boto.assign_funcs"](__name__, "sns", pack=__salt__)
    return has_boto_reqs


def get_all_topics(region=None, key=None, keyid=None, profile=None):
    """
    Returns a list of the all topics..

    CLI Example:

    .. code-block:: bash

        salt myminion boto_sns.get_all_topics
    """
    cache_key = _cache_get_key()
    try:
        return __context__[cache_key]
    except KeyError:
        pass

    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
    __context__[cache_key] = {}
    # TODO: support >100 SNS topics (via NextToken)
    topics = conn.get_all_topics()
    for t in topics["ListTopicsResponse"]["ListTopicsResult"]["Topics"]:
        short_name = t["TopicArn"].split(":")[-1]
        __context__[cache_key][short_name] = t["TopicArn"]
    return __context__[cache_key]


def exists(name, region=None, key=None, keyid=None, profile=None):
    """
    Check to see if an SNS topic exists.

    CLI Example:

    .. code-block:: bash

        salt myminion boto_sns.exists mytopic region=us-east-1
    """
    topics = get_all_topics(region=region, key=key, keyid=keyid, profile=profile)
    if name.startswith("arn:aws:sns:"):
        return name in list(topics.values())
    else:
        return name in list(topics.keys())


def create(name, region=None, key=None, keyid=None, profile=None):
    """
    Create an SNS topic.

    CLI example to create a topic::

        salt myminion boto_sns.create mytopic region=us-east-1
    """
    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
    conn.create_topic(name)
    log.info("Created SNS topic %s", name)
    _invalidate_cache()
    return True


def delete(name, region=None, key=None, keyid=None, profile=None):
    """
    Delete an SNS topic.

    CLI example to delete a topic::

        salt myminion boto_sns.delete mytopic region=us-east-1
    """
    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
    conn.delete_topic(get_arn(name, region, key, keyid, profile))
    log.info("Deleted SNS topic %s", name)
    _invalidate_cache()
    return True


def get_all_subscriptions_by_topic(
    name, region=None, key=None, keyid=None, profile=None
):
    """
    Get list of all subscriptions to a specific topic.

    CLI example to delete a topic::

        salt myminion boto_sns.get_all_subscriptions_by_topic mytopic region=us-east-1
    """
    cache_key = _subscriptions_cache_key(name)
    try:
        return __context__[cache_key]
    except KeyError:
        pass

    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
    ret = conn.get_all_subscriptions_by_topic(
        get_arn(name, region, key, keyid, profile)
    )
    __context__[cache_key] = ret["ListSubscriptionsByTopicResponse"][
        "ListSubscriptionsByTopicResult"
    ]["Subscriptions"]
    return __context__[cache_key]


def subscribe(
    topic, protocol, endpoint, region=None, key=None, keyid=None, profile=None
):
    """
    Subscribe to a Topic.

    CLI example to delete a topic::

        salt myminion boto_sns.subscribe mytopic https https://www.example.com/sns-endpoint region=us-east-1
    """
    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
    conn.subscribe(get_arn(topic, region, key, keyid, profile), protocol, endpoint)
    log.info("Subscribe %s %s to %s topic", protocol, endpoint, topic)
    try:
        del __context__[_subscriptions_cache_key(topic)]
    except KeyError:
        pass
    return True


def unsubscribe(
    topic, subscription_arn, region=None, key=None, keyid=None, profile=None
):
    """
    Unsubscribe a specific SubscriptionArn of a topic.

    CLI Example:

    .. code-block:: bash

        salt myminion boto_sns.unsubscribe my_topic my_subscription_arn region=us-east-1

    .. versionadded:: 2016.11.0
    """
    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)

    if subscription_arn.startswith("arn:aws:sns:") is False:
        return False

    try:
        conn.unsubscribe(subscription_arn)
        log.info("Unsubscribe %s to %s topic", subscription_arn, topic)
    except Exception as e:  # pylint: disable=broad-except
        log.error("Unsubscribe Error", exc_info=True)
        return False
    else:
        __context__.pop(_subscriptions_cache_key(topic), None)
        return True


def get_arn(name, region=None, key=None, keyid=None, profile=None):
    """
    Returns the full ARN for a given topic name.

    CLI Example:

    .. code-block:: bash

        salt myminion boto_sns.get_arn mytopic
    """
    if name.startswith("arn:aws:sns:"):
        return name

    account_id = __salt__["boto_iam.get_account_id"](
        region=region, key=key, keyid=keyid, profile=profile
    )
    return f"arn:aws:sns:{_get_region(region, profile)}:{account_id}:{name}"


def _get_region(region=None, profile=None):
    if profile and "region" in profile:
        return profile["region"]
    if not region and __salt__["config.option"](profile):
        _profile = __salt__["config.option"](profile)
        region = _profile.get("region", None)
    if not region and __salt__["config.option"]("sns.region"):
        region = __salt__["config.option"]("sns.region")
    if not region:
        region = "us-east-1"
    return region


def _subscriptions_cache_key(name):
    return f"{_cache_get_key()}_{name}_subscriptions"


def _invalidate_cache():
    try:
        del __context__[_cache_get_key()]
    except KeyError:
        pass


def _cache_get_key():
    return "boto_sns.topics_cache"
