import sys
from collections import namedtuple

import pytest

from stripe._util import (
    dashboard_link,
    convert_to_dict,
    convert_to_stripe_object,
    logfmt,
    log_info,
    log_debug,
    sanitize_id,
)
from stripe import Balance
from stripe._api_mode import ApiMode
from stripe._util import get_api_mode
from stripe._stripe_object import StripeObject

LogTestCase = namedtuple("LogTestCase", "env flag should_output")
FmtTestCase = namedtuple("FmtTestCase", "props expected")


class TestUtil(object):
    DUMMY_REQ_ID = "req_qsxPhqHyLxcoaM"

    def test_test_apikey(self, mocker):
        mocker.patch("stripe.api_key", "sk_test_KOWobxXidxNlIx")
        link = dashboard_link(self.DUMMY_REQ_ID)
        assert (
            link
            == "https://dashboard.stripe.com/test/logs/" + self.DUMMY_REQ_ID
        )

    def test_live_apikey(self, mocker):
        mocker.patch("stripe.api_key", "sk_live_axwITqZSgTUXSN")
        link = dashboard_link(self.DUMMY_REQ_ID)
        assert (
            link
            == "https://dashboard.stripe.com/live/logs/" + self.DUMMY_REQ_ID
        )

    def test_no_apikey(self, mocker):
        mocker.patch("stripe.api_key", None)
        link = dashboard_link(self.DUMMY_REQ_ID)
        assert (
            link
            == "https://dashboard.stripe.com/test/logs/" + self.DUMMY_REQ_ID
        )

    def test_old_apikey(self, mocker):
        mocker.patch("stripe.api_key", "axwITqZSgTUXSN")
        link = dashboard_link(self.DUMMY_REQ_ID)
        assert (
            link
            == "https://dashboard.stripe.com/test/logs/" + self.DUMMY_REQ_ID
        )

    def log_test_loop(self, test_cases, logging_func, logger_name, mocker):
        for case in test_cases:
            try:
                logger_mock = mocker.patch(logger_name)
                print_mock = mocker.patch("builtins.print")
                mocker.patch("stripe.log", case.flag)
                mocker.patch("stripe._util.STRIPE_LOG", case.env)

                logging_func("foo \nbar", y=3)  # function under test

                if case.should_output:
                    print_mock.assert_called_once_with(
                        "message='foo \\nbar' y=3", file=sys.stderr
                    )
                else:
                    print_mock.assert_not_called()
                logger_mock.assert_called_once_with("message='foo \\nbar' y=3")
            finally:
                mocker.stopall()

    def test_log_debug(self, mocker):
        # (STRIPE_LOG, stripe.log): should_output?
        test_cases = [
            LogTestCase(env=None, flag=None, should_output=False),
            LogTestCase(env=None, flag="debug", should_output=True),
            LogTestCase(env=None, flag="info", should_output=False),
            LogTestCase(env="debug", flag=None, should_output=True),
            LogTestCase(env="debug", flag="debug", should_output=True),
            LogTestCase(env="debug", flag="info", should_output=False),
            LogTestCase(env="info", flag=None, should_output=False),
            LogTestCase(env="info", flag="debug", should_output=True),
            LogTestCase(env="info", flag="info", should_output=False),
        ]
        self.log_test_loop(
            test_cases,
            logging_func=log_debug,
            logger_name="stripe._util.logger.debug",
            mocker=mocker,
        )

    def test_log_info(self, mocker):
        # (STRIPE_LOG, stripe.log): should_output?
        test_cases = [
            LogTestCase(env=None, flag=None, should_output=False),
            LogTestCase(env=None, flag="debug", should_output=True),
            LogTestCase(env=None, flag="info", should_output=True),
            LogTestCase(env="debug", flag=None, should_output=True),
            LogTestCase(env="debug", flag="debug", should_output=True),
            LogTestCase(env="debug", flag="info", should_output=True),
            LogTestCase(env="info", flag=None, should_output=True),
            LogTestCase(env="info", flag="debug", should_output=True),
            LogTestCase(env="info", flag="info", should_output=True),
        ]
        self.log_test_loop(
            test_cases,
            logging_func=log_info,
            logger_name="stripe._util.logger.info",
            mocker=mocker,
        )

    def test_logfmt(self):
        cases = [
            FmtTestCase(
                props={"foo": "bar", "message": "yes"},
                expected="foo=bar message=yes",
            ),
            FmtTestCase(props={"foo": "bar baz"}, expected="foo='bar baz'"),
            FmtTestCase(
                props={"b": 2, "a": 1, "c": 3}, expected="a=1 b=2 c=3"
            ),
            FmtTestCase(
                props={"key with space": True},
                expected="'key with space'=True",
            ),
        ]
        for case in cases:
            result = logfmt(case.props)
            assert result == case.expected

    def test_convert_to_stripe_object_and_back(self):
        resp = {
            "object": "balance",
            "available": [
                {
                    "amount": 234,
                    "currency": "usd",
                    "source_types": {"card": 234},
                }
            ],
            "livemode": False,
        }

        obj = convert_to_stripe_object(resp, api_mode="V1")
        assert isinstance(obj, Balance)
        assert isinstance(obj.available, list)
        assert isinstance(obj.available[0], StripeObject)

        d = convert_to_dict(obj)
        assert isinstance(d, dict)
        assert isinstance(d["available"], list)
        assert isinstance(d["available"][0], dict)

        assert d == resp

    def test_sanitize_id(self):
        sanitized_id = sanitize_id("cu  %x 123")
        if isinstance(sanitized_id, bytes):
            sanitized_id = sanitized_id.decode("utf-8", "strict")
        assert sanitized_id == "cu++%25x+123"

    @pytest.mark.parametrize(
        ["url", "expected"],
        [
            ("/v2/core/events", "V2"),
            ("/v2/v1/core/events", "V2"),
            ("/v1/events", "V1"),
            ("/oauth/authorize", "V1"),
            ("something/v2/core/events", "V1"),
        ],
    )
    def test_get_api_mode(self, url: str, expected: ApiMode):
        assert get_api_mode(url) == expected
