import platform
import os

import mock

import hypothesis as h
import hypothesis.strategies as hs

import pytest

from .. import manifest, item, sourcefile, utils


def SourceFileWithTest(path, hash, cls, *args):
    s = mock.Mock(rel_path=path, hash=hash)
    test = cls(s, utils.rel_path_to_url(path), *args)
    s.manifest_items = mock.Mock(return_value=(cls.item_type, [test]))
    return s


@hs.composite
def rel_dir_file_path(draw):
    length = draw(hs.integers(min_value=1, max_value=20))
    if length == 1:
        return "a"
    else:
        remaining = length - 2
        if os.path.sep == "/":
            alphabet = "a/"
        elif os.path.sep == "\\":
            alphabet = "a/\\"
        else:
            assert False, "uhhhh, this platform is weird"
        mid = draw(hs.text(alphabet=alphabet, min_size=remaining, max_size=remaining))
        return os.path.normcase("a" + mid + "a")


@hs.composite
def sourcefile_strategy(draw):
    item_classes = [item.TestharnessTest, item.RefTest, item.RefTestNode,
                    item.ManualTest, item.Stub, item.WebdriverSpecTest,
                    item.ConformanceCheckerTest, item.SupportFile]
    cls = draw(hs.sampled_from(item_classes))

    path = draw(rel_dir_file_path())
    hash = draw(hs.text(alphabet="0123456789abcdef", min_size=40, max_size=40))
    s = mock.Mock(rel_path=path, hash=hash)

    if cls in (item.RefTest, item.RefTestNode):
        ref_path = draw(rel_dir_file_path())
        h.assume(path != ref_path)
        ref_eq = draw(hs.sampled_from(["==", "!="]))
        test = cls(s, utils.rel_path_to_url(path), [(utils.rel_path_to_url(ref_path), ref_eq)])
    elif cls is item.SupportFile:
        test = cls(s)
    else:
        test = cls(s, utils.rel_path_to_url(path))

    s.manifest_items = mock.Mock(return_value=(cls.item_type, [test]))
    return s


@h.given(hs.lists(sourcefile_strategy(),
                  min_size=1, average_size=10, max_size=1000,
                  unique_by=lambda x: x.rel_path))
@h.example([SourceFileWithTest("a", "0"*40, item.ConformanceCheckerTest)])
def test_manifest_to_json(s):
    m = manifest.Manifest()

    assert m.update(s) is True

    json_str = m.to_json()
    loaded = manifest.Manifest.from_json("/", json_str)

    assert list(loaded) == list(m)

    assert loaded.to_json() == json_str


@h.given(hs.lists(sourcefile_strategy(),
                  min_size=1, average_size=10,
                  unique_by=lambda x: x.rel_path))
@h.example([SourceFileWithTest("a", "0"*40, item.TestharnessTest)])
@h.example([SourceFileWithTest("a", "0"*40, item.RefTest, [("/aa", "==")])])
def test_manifest_idempotent(s):
    m = manifest.Manifest()

    assert m.update(s) is True

    m1 = list(m)

    assert m.update(s) is False

    assert list(m) == m1


def test_manifest_to_json_forwardslash():
    m = manifest.Manifest()

    s = SourceFileWithTest("a/b", "0"*40, item.TestharnessTest)

    assert m.update([s]) is True

    assert m.to_json() == {
        'paths': {
            'a/b': ('0000000000000000000000000000000000000000', 'testharness')
        },
        'version': 4,
        'url_base': '/',
        'items': {
            'reftest': {},
            'reftest_node': {},
            'testharness': {
                'a/b': [['/a/b', {}]]
            }
        }
    }


def test_manifest_to_json_backslash():
    m = manifest.Manifest()

    s = SourceFileWithTest("a\\b", "0"*40, item.TestharnessTest)

    if os.path.sep == "\\":
        assert m.update([s]) is True

        assert m.to_json() == {
            'paths': {
                'a/b': ('0000000000000000000000000000000000000000', 'testharness')
            },
            'version': 4,
            'url_base': '/',
            'items': {
                'reftest': {},
                'reftest_node': {},
                'testharness': {
                    'a/b': [['/a/b', {}]]
                }
            }
        }
    else:
        with pytest.raises(ValueError):
            # one of these must raise ValueError
            # the first must return True if it doesn't raise
            assert m.update([s]) is True
            m.to_json()


def test_manifest_from_json_backslash():
    json_obj = {
        'paths': {
            'a\\b': ('0000000000000000000000000000000000000000', 'testharness')
        },
        'version': 4,
        'url_base': '/',
        'items': {
            'reftest': {},
            'reftest_node': {},
            'testharness': {
                'a\\b': [['/a/b', {}]]
            }
        }
    }

    with pytest.raises(ValueError):
        manifest.Manifest.from_json("/", json_obj)


def test_reftest_computation_chain():
    m = manifest.Manifest()

    s1 = SourceFileWithTest("test1", "0"*40, item.RefTest, [("/test2", "==")])
    s2 = SourceFileWithTest("test2", "0"*40, item.RefTest, [("/test3", "==")])

    m.update([s1, s2])

    test1 = s1.manifest_items()[1][0]
    test2 = s2.manifest_items()[1][0]
    test2_node = test2.to_RefTestNode()

    assert list(m) == [("reftest", test1.path, {test1}),
                       ("reftest_node", test2.path, {test2_node})]


def test_reftest_computation_chain_update_add():
    m = manifest.Manifest()

    s2 = SourceFileWithTest("test2", "0"*40, item.RefTest, [("/test3", "==")])
    test2 = s2.manifest_items()[1][0]

    assert m.update([s2]) is True

    assert list(m) == [("reftest", test2.path, {test2})]

    s1 = SourceFileWithTest("test1", "0"*40, item.RefTest, [("/test2", "==")])
    test1 = s1.manifest_items()[1][0]

    # s2's hash is unchanged, but it has gone from a test to a node
    assert m.update([s1, s2]) is True

    test2_node = test2.to_RefTestNode()

    assert list(m) == [("reftest", test1.path, {test1}),
                       ("reftest_node", test2.path, {test2_node})]


def test_reftest_computation_chain_update_remove():
    m = manifest.Manifest()

    s1 = SourceFileWithTest("test1", "0"*40, item.RefTest, [("/test2", "==")])
    s2 = SourceFileWithTest("test2", "0"*40, item.RefTest, [("/test3", "==")])

    assert m.update([s1, s2]) is True

    test1 = s1.manifest_items()[1][0]
    test2 = s2.manifest_items()[1][0]
    test2_node = test2.to_RefTestNode()

    assert list(m) == [("reftest", test1.path, {test1}),
                       ("reftest_node", test2.path, {test2_node})]

    # s2's hash is unchanged, but it has gone from a node to a test
    assert m.update([s2]) is True

    assert list(m) == [("reftest", test2.path, {test2})]
