"""
Pearson Service Insight's Cloudability API Module
This module contains a class with methods to interact with the Cloudability API.
"""
import base64
import json
import logging
import os
import sys

from typing import List

import requests

CLOUDABILITY_V1_API_URL = os.environ.get('CLOUDABILITY_API_URL', 'https://app.cloudability.com/api/1')
CLOUDABILITY_V3_API_URL = os.environ.get('CLOUDABILITY_V3_API_URL', 'https://api.cloudability.com/v3')


class Cloudability:

    def __init__(self, api_key: str, logger: logging.Logger = None):

        self._logger = logger if logger else self._get_logger()
        self._headers = {
            'Accept': 'application/json',
            'Authorization': f'Basic {base64.b64encode(api_key.encode("utf-8")).decode(("utf-8"))}',
            'Content-Type': 'application/json'
        }

    def get_scorecards(self, grouping_dimension: str, timeline: str = 'ten-day') -> dict:
        """Return all results from the v3/scorecards endpoint as a dict

        curl "https://api.cloudability.com/v3/scorecards?dimension=tag1&timeline=ten-day" -u <api_key>

        Parameters
        ----------
        grouping_dimension : str
            The name of a valid grouping dimension, e.x 'tag1'.
            See list_measures for more information on dimensions

        timeline : str
            A time range to evaluate scorecards against (default 'ten-day')
            (values 'ten-day', 'thirty-day')


        Returns
        -------
            result
        """
        if timeline not in ('ten-day', 'thirty day'):
            raise ValueError("timeline must be either 'ten-day' or 'thirty-day'")

        url = CLOUDABILITY_V3_API_URL + '/scorecards'
        response = requests.get(url,
                                headers=self._headers,
                                params={
                                    'dimension': grouping_dimension,
                                    'timeline': timeline
                                })
        self._logger.debug(response.request.headers)
        self._logger.info(f'{response.request.method} {response.request.url}')
        response.raise_for_status()
        return response.json()

    def list_measures(self, endpoint: str = '/internal/reporting/util/measures') -> List[dict]:
        """Return all measures (dimensions, metrics) known by Cloudability.

        curl "https://api.cloudability.com/v3/internal/reporting/util/measures" -u <api_key>

        The API is a tangled mess of seperate and missing endpoints for metrics
        and dimensions. For example, you can get Business Mapping dimensions
        from the V3 business-mappings/dimensions endpoint, however that
        endpoint only returns business dimension "label"s and does not include
        the dimension "name" required by all other API endpoint parameters.
        Another example is the Account Groups dimension. this endpoint only
        exists on the V1 API, and also only provides the demension "name".
        Tag, and Organizational/Vendor dimensions are missing endpoints entirely.


        Cloudability has three distinct endpoints for querying /all/ measures,
        each returning varrying amounts of data, from highest to lowest:

        https://api.cloudability.com/v3/internal/reporting/util/measures

        https://app.cloudability.com/api/1/reporting/cost/measures

        https://app.cloudability.com/api/1/reporting/measures

        the V3 /internal/reporting/util/measures endpoint is used by default as
        it returns the largest dataset, with 65 metrics and 266 dimensions at
        the time of writing.

        Parameters
        ----------
        endpoint : str
            Control which endpoint to retrieve measures from
            (default '/internal/reporting/util/measures')
            (values '/reporting/measures', '/reporting/cost/measures', '/internal/reporting/util/measures')

        Returns
        -------
        List[Dict]
            [
                {
                    "label": "Application (value)",
                    "name": "tag1",
                    "description": null,
                    "data_type": "string",
                    "type": "dimension",
                    "group": {
                        "cube_type": "usage",
                        "id": 1,
                        "key": "common",
                        "name": "Common"
                    },
                    "sub_group": {
                        "cube_type": "usage",
                        "id": 6,
                        "key": "tags",
                        "name": "Tags"
                    }
                },
                {
                    "label": "Realized Savings",
                    "name": "business_metric1",
                    "description": null,
                    "data_type": "currency",
                    "type": "metric",
                    "group": {
                        "cube_type": "cost",
                        "id": 1,
                        "key": "common",
                        "name": "Common"
                    },
                    "sub_group": {
                        "cube_type": "cost",
                        "id": 11,
                        "key": "business metrics",
                        "name": "Business Metrics"
                    }
                }
            ]

        Tag mapping entries are returned as dimension entries.
        The "label" field shows the description of a given metric/dimension as
        shown in the Cloudability interface, however the "name" field should be
        used when providing dimension/metric in an API call.
        """
        if endpoint in ('/reporting/measures', '/reporting/cost/measures'):
            url = CLOUDABILITY_V1_API_URL + endpoint
        elif endpoint == '/internal/reporting/util/measures':
            url = CLOUDABILITY_V3_API_URL + endpoint
        else:
            raise ValueError(f"endpoint must be one of /reporting/measures', '/reporting/cost/measures' or '/internal/reporting/util/measures'. \n\
                Got: {endpoint}")

        response = requests.get(url, headers=self._headers)
        self._logger.debug(response.request.headers)
        self._logger.info(f'{response.request.method} {response.request.url}')
        response.raise_for_status()
        return response.json()

    def _get_logger(self, log_level: str = 'info') -> logging.Logger:
        """Return a logger with the given logging level set"""
        handler = logging.StreamHandler(sys.stdout)
        log = logging.getLogger('prometheus_exporter')
        log_level = log_level.lower()
        if log_level == 'debug':
            log.setLevel(logging.DEBUG)
            handler.setLevel(logging.DEBUG)
        elif log_level == 'warning':
            log.setLevel(logging.WARNING)
            handler.setLevel(logging.WARNING)
        elif log_level == 'error':
            log.setLevel(logging.ERROR)
            handler.setLevel(logging.ERROR)
        elif log_level == 'critical':
            log.setLevel(logging.CRITICAL)
            handler.setLevel(logging.CRITICAL)
        else:
            log.setLevel(logging.INFO)
            handler.setLevel(logging.INFO)

        formatter = logging.Formatter(
            '%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s')
        handler.setFormatter(formatter)
        log.addHandler(handler)
        log.propagate = False
        return log

    def __repr__(self) -> str:
        """Return a string representation of the object"""
        return f"Cloudability()"


def main():
    api = Cloudability('')
    results = api.list_measures()
    print(json.dumps(results, indent=4))


if __name__ == '__main__':
    main()
