#   Copyright 2023 Red Hat, Inc.
#
#   Licensed under the Apache License, Version 2.0 (the "License"); you may
#   not use this file except in compliance with the License. You may obtain
#   a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#   License for the specific language governing permissions and limitations
#   under the License.

import logging
import os
from urllib import parse

from keystoneauth1 import adapter
from keystoneauth1.exceptions import catalog as keystone_exception
from oslo_utils import netutils
import yaml

from observabilityclient.prometheus_client import PrometheusAPIClient


DEFAULT_CONFIG_LOCATIONS = (
    [os.path.join(os.environ["HOME"], ".config/openstack/"), "/etc/openstack/"]
    if "HOME" in os.environ
    else ["/etc/openstack/"]
)
CONFIG_FILE_NAME = "prometheus.yaml"
LOG = logging.getLogger(__name__)


class ConfigurationError(Exception):
    pass


def get_config_file():
    if os.path.exists(CONFIG_FILE_NAME):
        LOG.debug("Using %s as prometheus configuration", CONFIG_FILE_NAME)
        return open(CONFIG_FILE_NAME)
    for path in DEFAULT_CONFIG_LOCATIONS:
        full_filename = path + CONFIG_FILE_NAME
        if os.path.exists(full_filename):
            LOG.debug("Using %s as prometheus configuration", full_filename)
            return open(full_filename)
    return None


def get_prom_client_from_keystone(session, adapter_options=None):
    if adapter_options is None:
        adapter_options = {}
    endpoint = adapter.Adapter(
        session=session, **adapter_options
    ).get_endpoint()
    parsed_url = parse.urlparse(endpoint)

    escaped_host = netutils.escape_ipv6(parsed_url.hostname)
    root_path = parsed_url.path.strip('/')

    if parsed_url.port is not None:
        url = f'{escaped_host}:{parsed_url.port}'
    else:
        url = escaped_host

    return PrometheusAPIClient(
        url, session, root_path, scheme=parsed_url.scheme)


def get_prom_client_from_file_or_env():
    host = port = ca_cert = None
    root_path = ''
    conf_file = get_config_file()
    if conf_file is not None:
        conf = yaml.safe_load(conf_file)
        if 'host' in conf:
            host = conf['host']
        if 'port' in conf:
            port = conf['port']
        if 'ca_cert' in conf:
            ca_cert = conf['ca_cert']
        if 'root_path' in conf:
            root_path = conf['root_path']
        conf_file.close()
    if 'PROMETHEUS_HOST' in os.environ:
        host = os.environ['PROMETHEUS_HOST']
    if 'PROMETHEUS_PORT' in os.environ:
        port = os.environ['PROMETHEUS_PORT']
    if 'PROMETHEUS_CA_CERT' in os.environ:
        ca_cert = os.environ['PROMETHEUS_CA_CERT']
    if 'PROMETHEUS_ROOT_PATH' in os.environ:
        root_path = os.environ['PROMETHEUS_ROOT_PATH']
    if host is None or port is None:
        raise ConfigurationError("Can't find prometheus host and "
                                 "port configuration in config file or "
                                 "environment variables.")
    escaped_host = netutils.escape_ipv6(host)
    client = PrometheusAPIClient(
        f"{escaped_host}:{port}", None, root_path
    )
    if ca_cert is not None:
        client.set_ca_cert(ca_cert)
    return client


def get_prometheus_client(session=None, adapter_options=None):
    if adapter_options is None:
        adapter_options = {}

    keystone_error = None
    if session is not None:
        try:
            return get_prom_client_from_keystone(session, adapter_options)
        except keystone_exception.EndpointNotFound as e:
            # NOTE(jwysogla): Fallback to get the endpoint configuration from
            # the config file and env vars. If that doesn't work, the error
            # message is raised as ConfigurationError later.
            keystone_error = e
            LOG.debug("Aetos endpoint discovery from Keystone failed: %s", e)

    # NOTE(jwysogla): Always fallback to the original method of discovery
    # through config file and env variables for backwards compatibility.
    try:
        return get_prom_client_from_file_or_env()
    except ConfigurationError as e:
        if keystone_error is not None:
            raise ConfigurationError(
                f"Failed to configure Prometheus client. "
                f"Aetos discovery from keystone failed: '{keystone_error}'. "
                f"Prometheus configuration from config file and environment "
                f"variables failed: '{e}'"
            )
        else:
            raise e


def get_client(obj):
    return obj.app.client_manager.observabilityclient


def format_labels(d: dict) -> str:
    def replace_doubled_quotes(string):
        if "''" in string:
            string = string.replace("''", "'")
        if '""' in string:
            string = string.replace('""', '"')
        return string

    ret = ""
    for key, value in d.items():
        ret += f"{key}='{value}', "
    ret = ret[0:-2]
    old = ""
    while ret != old:
        old = ret
        ret = replace_doubled_quotes(ret)
    return ret


def metrics2cols(m):
    # get all label keys
    cols = list(set().union(*(d.labels.keys() for d in m)))
    cols.sort()
    cols.append("value")
    fields = []
    for metric in m:
        row = [""] * len(cols)
        for key, value in metric.labels.items():
            row[cols.index(key)] = value
        row[cols.index("value")] = metric.value
        fields.append(row)
    return cols, fields
