# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.

from collections.abc import Generator
import io
import logging
import os
import pprint
import sys
from typing import Any, TextIO

import fixtures
from oslotest import base
import testtools.content

from openstack.tests import fixtures as os_fixtures
from openstack import utils

_TRUE_VALUES = ('true', '1', 'yes')


class TestCase(base.BaseTestCase):
    """Test case base class for all tests."""

    # A way to adjust slow test classes
    TIMEOUT_SCALING_FACTOR = 1

    def setUp(self) -> None:
        """Run before each test method to initialize test environment."""
        # No openstacksdk unit tests should EVER run longer than a second.
        # Set this to 5 by default just to give us some fudge.
        # Do this before super setUp so that we intercept the default value
        # in oslotest. TODO(mordred) Make the default timeout configurable
        # in oslotest.
        test_timeout = int(os.environ.get('OS_TEST_TIMEOUT', '5'))
        try:
            test_timeout = int(test_timeout * self.TIMEOUT_SCALING_FACTOR)
            self.useFixture(
                fixtures.EnvironmentVariable(
                    'OS_TEST_TIMEOUT', str(test_timeout)
                )
            )
        except ValueError:
            # Let oslotest do its thing
            pass

        super().setUp()

        self.warnings = self.useFixture(os_fixtures.WarningsFixture())

        self._log_stream: TextIO

        if os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES:
            self._log_stream = io.StringIO()
            if os.environ.get('OS_ALWAYS_LOG') in _TRUE_VALUES:
                self.addCleanup(self.printLogs)
            else:
                self.addOnException(self.attachLogs)
        else:
            self._log_stream = sys.stdout

        handler = logging.StreamHandler(self._log_stream)
        formatter = logging.Formatter('%(asctime)s %(name)-32s %(message)s')
        handler.setFormatter(formatter)

        logger = logging.getLogger('openstack')
        logger.setLevel(logging.DEBUG)
        logger.addHandler(handler)

        # Enable HTTP level tracing
        # TODO(mordred) This is blowing out our memory we think
        logger = logging.getLogger('keystoneauth')
        logger.setLevel(logging.INFO)
        logger.addHandler(handler)
        logger.propagate = False

    def _fake_logs(self) -> None:
        # Override _fake_logs in oslotest until we can get our
        # attach-on-exception logic added
        pass

    def assertEqual(
        self, expected: object, observed: object, message: str = ""
    ) -> None:
        """Munch aware wrapper"""
        if isinstance(expected, utils.Munch):
            expected = expected.toDict()
        if isinstance(observed, utils.Munch):
            observed = observed.toDict()
        return super().assertEqual(expected, observed, message)

    def printLogs(self, *args: Any) -> None:
        self._log_stream.seek(0)
        print(self._log_stream.read())

    def attachLogs(self, *args: Any) -> None:
        def reader() -> Generator[bytes, None, None]:
            self._log_stream.seek(0)
            while True:
                x = self._log_stream.read(4096)
                if not x:
                    break
                yield x.encode('utf8')

        content = testtools.content.content_from_reader(
            reader, testtools.content_type.UTF8_TEXT, False
        )
        self.addDetail('logging', content)

    def add_info_on_exception(self, name: str, text: object) -> None:
        def add_content(unused: Any) -> None:
            self.addDetail(
                name, testtools.content.text_content(pprint.pformat(text))
            )

        self.addOnException(add_content)

    def assertSubdict(
        self, part: dict[str, object], whole: dict[str, object]
    ) -> None:
        missing_keys = []
        for key in part:
            # In the resource we have virtual access by not existing keys. To
            # verify those are there try access it.
            if not whole[key] and part[key]:
                missing_keys.append(key)
        if missing_keys:
            self.fail(f"Keys {missing_keys} are in {part} but not in {whole}")
        wrong_values = [
            (key, part[key], whole[key])
            for key in part
            if part[key] != whole[key]
        ]
        if wrong_values:
            self.fail(
                "Mismatched values: {}".format(
                    ", ".join(
                        "for {} got {} and {}".format(*tpl)
                        for tpl in wrong_values
                    )
                )
            )
