import pytest
import os
import typing
import contextlib

from mitmproxy.test import tutils
import mitmproxy.exceptions
import mitmproxy.types
from mitmproxy.test import taddons
from mitmproxy.test import tflow
from mitmproxy import command
from mitmproxy import flow

from . import test_command


@contextlib.contextmanager
def chdir(path: str):
    old_dir = os.getcwd()
    os.chdir(path)
    yield
    os.chdir(old_dir)


def test_bool():
    with taddons.context() as tctx:
        b = mitmproxy.types._BoolType()
        assert b.completion(tctx.master.commands, bool, "b") == ["false", "true"]
        assert b.parse(tctx.master.commands, bool, "true") is True
        assert b.parse(tctx.master.commands, bool, "false") is False
        assert b.is_valid(tctx.master.commands, bool, True) is True
        assert b.is_valid(tctx.master.commands, bool, "foo") is False
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, bool, "foo")


def test_str():
    with taddons.context() as tctx:
        b = mitmproxy.types._StrType()
        assert b.is_valid(tctx.master.commands, str, "foo") is True
        assert b.is_valid(tctx.master.commands, str, 1) is False
        assert b.completion(tctx.master.commands, str, "") == []
        assert b.parse(tctx.master.commands, str, "foo") == "foo"


def test_unknown():
    with taddons.context() as tctx:
        b = mitmproxy.types._UnknownType()
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Unknown, "foo") is False
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Unknown, 1) is False
        assert b.completion(tctx.master.commands, mitmproxy.types.Unknown, "") == []
        assert b.parse(tctx.master.commands, mitmproxy.types.Unknown, "foo") == "foo"


def test_int():
    with taddons.context() as tctx:
        b = mitmproxy.types._IntType()
        assert b.is_valid(tctx.master.commands, int, "foo") is False
        assert b.is_valid(tctx.master.commands, int, 1) is True
        assert b.completion(tctx.master.commands, int, "b") == []
        assert b.parse(tctx.master.commands, int, "1") == 1
        assert b.parse(tctx.master.commands, int, "999") == 999
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, int, "foo")


def test_path():
    with taddons.context() as tctx:
        b = mitmproxy.types._PathType()
        assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/foo") == "/foo"
        assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/bar") == "/bar"
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, "foo") is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, 3) is False

        def normPathOpts(prefix, match):
            ret = []
            for s in b.completion(tctx.master.commands, mitmproxy.types.Path, match):
                s = s[len(prefix):]
                s = s.replace(os.sep, "/")
                ret.append(s)
            return ret

        cd = os.path.normpath(tutils.test_data.path("mitmproxy/completion"))
        assert normPathOpts(cd, cd) == ['/aaa', '/aab', '/aac', '/bbb/']
        assert normPathOpts(cd, os.path.join(cd, "a")) == ['/aaa', '/aab', '/aac']
        with chdir(cd):
            assert normPathOpts("", "./") == ['./aaa', './aab', './aac', './bbb/']
            assert normPathOpts("", "") == ['./aaa', './aab', './aac', './bbb/']
        assert b.completion(
            tctx.master.commands, mitmproxy.types.Path, "nonexistent"
        ) == ["nonexistent"]


def test_cmd():
    with taddons.context() as tctx:
        tctx.master.addons.add(test_command.TAddon())
        b = mitmproxy.types._CmdType()
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Cmd, "foo") is False
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Cmd, "cmd1") is True
        assert b.parse(tctx.master.commands, mitmproxy.types.Cmd, "cmd1") == "cmd1"
        with pytest.raises(mitmproxy.exceptions.TypeError):
            assert b.parse(tctx.master.commands, mitmproxy.types.Cmd, "foo")
        assert len(
            b.completion(tctx.master.commands, mitmproxy.types.Cmd, "")
        ) == len(tctx.master.commands.commands.keys())


def test_cutspec():
    with taddons.context() as tctx:
        b = mitmproxy.types._CutSpecType()
        b.parse(tctx.master.commands, mitmproxy.types.CutSpec, "foo,bar") == ["foo", "bar"]
        assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, 1) is False
        assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, "foo") is False
        assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, "request.path") is True

        assert b.completion(
            tctx.master.commands, mitmproxy.types.CutSpec, "request.p"
        ) == b.valid_prefixes
        ret = b.completion(tctx.master.commands, mitmproxy.types.CutSpec, "request.port,f")
        assert ret[0].startswith("request.port,")
        assert len(ret) == len(b.valid_prefixes)


def test_arg():
    with taddons.context() as tctx:
        b = mitmproxy.types._ArgType()
        assert b.completion(tctx.master.commands, mitmproxy.types.Arg, "") == []
        assert b.parse(tctx.master.commands, mitmproxy.types.Arg, "foo") == "foo"
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Arg, "foo") is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Arg, 1) is False


def test_strseq():
    with taddons.context() as tctx:
        b = mitmproxy.types._StrSeqType()
        assert b.completion(tctx.master.commands, typing.Sequence[str], "") == []
        assert b.parse(tctx.master.commands, typing.Sequence[str], "foo") == ["foo"]
        assert b.parse(tctx.master.commands, typing.Sequence[str], "foo,bar") == ["foo", "bar"]
        assert b.is_valid(tctx.master.commands, typing.Sequence[str], ["foo"]) is True
        assert b.is_valid(tctx.master.commands, typing.Sequence[str], ["a", "b", 3]) is False
        assert b.is_valid(tctx.master.commands, typing.Sequence[str], 1) is False
        assert b.is_valid(tctx.master.commands, typing.Sequence[str], "foo") is False


class DummyConsole:
    @command.command("view.resolve")
    def resolve(self, spec: str) -> typing.Sequence[flow.Flow]:
        if spec == "err":
            raise mitmproxy.exceptions.CommandError()
        n = int(spec)
        return [tflow.tflow(resp=True)] * n

    @command.command("cut")
    def cut(self, spec: str) -> mitmproxy.types.Data:
        return [["test"]]

    @command.command("options")
    def options(self) -> typing.Sequence[str]:
        return ["one", "two", "three"]


def test_flow():
    with taddons.context() as tctx:
        tctx.master.addons.add(DummyConsole())
        b = mitmproxy.types._FlowType()
        assert len(b.completion(tctx.master.commands, flow.Flow, "")) == len(b.valid_prefixes)
        assert b.parse(tctx.master.commands, flow.Flow, "1")
        assert b.is_valid(tctx.master.commands, flow.Flow, tflow.tflow()) is True
        assert b.is_valid(tctx.master.commands, flow.Flow, "xx") is False
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, flow.Flow, "0")
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, flow.Flow, "2")
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, flow.Flow, "err")


def test_flows():
    with taddons.context() as tctx:
        tctx.master.addons.add(DummyConsole())
        b = mitmproxy.types._FlowsType()
        assert len(
            b.completion(tctx.master.commands, typing.Sequence[flow.Flow], "")
        ) == len(b.valid_prefixes)
        assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], [tflow.tflow()]) is True
        assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], "xx") is False
        assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], 0) is False
        assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "0")) == 0
        assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "1")) == 1
        assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "2")) == 2
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "err")


def test_data():
    with taddons.context() as tctx:
        b = mitmproxy.types._DataType()
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, 0) is False
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, []) is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [["x"]]) is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [[b"x"]]) is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [[1]]) is False
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")


def test_choice():
    with taddons.context() as tctx:
        tctx.master.addons.add(DummyConsole())
        b = mitmproxy.types._ChoiceType()
        assert b.is_valid(
            tctx.master.commands,
            mitmproxy.types.Choice("options"),
            "one",
        ) is True
        assert b.is_valid(
            tctx.master.commands,
            mitmproxy.types.Choice("options"),
            "invalid",
        ) is False
        assert b.is_valid(
            tctx.master.commands,
            mitmproxy.types.Choice("nonexistent"),
            "invalid",
        ) is False
        comp = b.completion(tctx.master.commands, mitmproxy.types.Choice("options"), "")
        assert comp == ["one", "two", "three"]
        assert b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "one") == "one"
        with pytest.raises(mitmproxy.exceptions.TypeError):
            b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "invalid")


def test_typemanager():
    assert mitmproxy.types.CommandTypes.get(bool, None)
    assert mitmproxy.types.CommandTypes.get(mitmproxy.types.Choice("choide"), None)
