import contextlib
import os
from collections.abc import Sequence

import pytest

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


@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(ValueError):
            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"
        assert b.parse(tctx.master.commands, str, r"foo\nbar") == "foo\nbar"
        assert b.parse(tctx.master.commands, str, r"\N{BELL}") == "🔔"
        with pytest.raises(ValueError):
            b.parse(tctx.master.commands, bool, r"\N{UNKNOWN UNICODE SYMBOL!}")


def test_bytes():
    with taddons.context() as tctx:
        b = mitmproxy.types._BytesType()
        assert b.is_valid(tctx.master.commands, bytes, b"foo") is True
        assert b.is_valid(tctx.master.commands, bytes, 1) is False
        assert b.completion(tctx.master.commands, bytes, "") == []
        assert b.parse(tctx.master.commands, bytes, "foo") == b"foo"
        with pytest.raises(ValueError):
            b.parse(tctx.master.commands, bytes, "incomplete escape sequence\\")


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(ValueError):
            b.parse(tctx.master.commands, int, "foo")


def test_path(tdata, monkeypatch):
    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"
        monkeypatch.setenv("HOME", "/home/test")
        monkeypatch.setenv("USERPROFILE", "/home/test")
        assert (
            b.parse(tctx.master.commands, mitmproxy.types.Path, "~/mitm")
            == "/home/test/mitm"
        )
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, "foo") is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, "~/mitm") 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(tdata.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(ValueError):
            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_marker():
    with taddons.context() as tctx:
        b = mitmproxy.types._MarkerType()
        assert (
            b.parse(tctx.master.commands, mitmproxy.types.Marker, ":red_circle:")
            == ":red_circle:"
        )
        assert (
            b.parse(tctx.master.commands, mitmproxy.types.Marker, "true") == ":default:"
        )
        assert b.parse(tctx.master.commands, mitmproxy.types.Marker, "false") == ""

        with pytest.raises(ValueError):
            b.parse(tctx.master.commands, mitmproxy.types.Marker, ":bogus:")

        assert b.is_valid(tctx.master.commands, mitmproxy.types.Marker, "true") is True
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Marker, "false") is True
        assert (
            b.is_valid(tctx.master.commands, mitmproxy.types.Marker, "bogus") is False
        )
        assert b.is_valid(tctx.master.commands, mitmproxy.types.Marker, "X") is True
        assert (
            b.is_valid(tctx.master.commands, mitmproxy.types.Marker, ":red_circle:")
            is True
        )
        ret = b.completion(tctx.master.commands, mitmproxy.types.Marker, ":smil")
        assert len(ret) > 10


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


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


class DummyConsole:
    @command.command("view.flows.resolve")
    def resolve(self, spec: str) -> Sequence[flow.Flow]:
        if spec == "err":
            raise mitmproxy.exceptions.CommandError()
        try:
            n = int(spec)
        except ValueError:
            n = 1
        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) -> 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.parse(tctx.master.commands, flow.Flow, "has space")
        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(ValueError):
            b.parse(tctx.master.commands, flow.Flow, "0")
        with pytest.raises(ValueError):
            b.parse(tctx.master.commands, flow.Flow, "2")
        with pytest.raises(ValueError):
            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, Sequence[flow.Flow], "")) == len(
            b.valid_prefixes
        )
        assert (
            b.is_valid(tctx.master.commands, Sequence[flow.Flow], [tflow.tflow()])
            is True
        )
        assert b.is_valid(tctx.master.commands, Sequence[flow.Flow], "xx") is False
        assert b.is_valid(tctx.master.commands, Sequence[flow.Flow], 0) is False
        assert len(b.parse(tctx.master.commands, Sequence[flow.Flow], "0")) == 0
        assert len(b.parse(tctx.master.commands, Sequence[flow.Flow], "1")) == 1
        assert len(b.parse(tctx.master.commands, Sequence[flow.Flow], "2")) == 2
        assert len(b.parse(tctx.master.commands, Sequence[flow.Flow], "has space")) == 1
        with pytest.raises(ValueError):
            b.parse(tctx.master.commands, 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(ValueError):
            b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
        with pytest.raises(ValueError):
            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(ValueError):
            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)
