import json
from textwrap import dedent

import hypothesis.strategies as st
from hypothesis import given, settings

from vdirsyncer import exceptions
from vdirsyncer.storage.base import Storage


def test_discover_command(tmpdir, runner):
    runner.write_with_general(dedent('''
    [storage foo]
    type = "filesystem"
    path = "{0}/foo/"
    fileext = ".txt"

    [storage bar]
    type = "filesystem"
    path = "{0}/bar/"
    fileext = ".txt"

    [pair foobar]
    a = "foo"
    b = "bar"
    collections = ["from a"]
    ''').format(str(tmpdir)))

    foo = tmpdir.mkdir('foo')
    bar = tmpdir.mkdir('bar')

    for x in 'abc':
        foo.mkdir(x)
        bar.mkdir(x)
    bar.mkdir('d')

    result = runner.invoke(['discover'])
    assert not result.exception

    foo.mkdir('d')
    result = runner.invoke(['sync'])
    assert not result.exception
    lines = result.output.splitlines()
    assert 'Syncing foobar/a' in lines
    assert 'Syncing foobar/b' in lines
    assert 'Syncing foobar/c' in lines
    assert 'Syncing foobar/d' not in result.output

    result = runner.invoke(['discover'])
    assert not result.exception

    result = runner.invoke(['sync'])
    assert not result.exception
    assert 'Syncing foobar/a' in lines
    assert 'Syncing foobar/b' in lines
    assert 'Syncing foobar/c' in lines
    assert 'Syncing foobar/d' in result.output

    # Check for redundant data that is already in the config. This avoids
    # copying passwords from the config too.
    assert 'fileext' not in tmpdir \
        .join('status') \
        .join('foobar.collections') \
        .read()


def test_discover_different_collection_names(tmpdir, runner):
    foo = tmpdir.mkdir('foo')
    bar = tmpdir.mkdir('bar')
    runner.write_with_general(dedent('''
    [storage foo]
    type = "filesystem"
    fileext = ".txt"
    path = "{foo}"

    [storage bar]
    type = "filesystem"
    fileext = ".txt"
    path = "{bar}"

    [pair foobar]
    a = "foo"
    b = "bar"
    collections = [
        ["coll1", "coll_a1", "coll_b1"],
        "coll2"
     ]
    ''').format(foo=str(foo), bar=str(bar)))

    result = runner.invoke(['discover'], input='y\n' * 6)
    assert not result.exception

    coll_a1 = foo.join('coll_a1')
    coll_b1 = bar.join('coll_b1')

    assert coll_a1.exists()
    assert coll_b1.exists()

    result = runner.invoke(['sync'])
    assert not result.exception

    foo_txt = coll_a1.join('foo.txt')
    foo_txt.write('BEGIN:VCALENDAR\nUID:foo\nEND:VCALENDAR')

    result = runner.invoke(['sync'])
    assert not result.exception

    assert foo_txt.exists()
    assert coll_b1.join('foo.txt').exists()


def test_discover_direct_path(tmpdir, runner):
    foo = tmpdir.join('foo')
    bar = tmpdir.join('bar')

    runner.write_with_general(dedent('''
    [storage foo]
    type = "filesystem"
    fileext = ".txt"
    path = "{foo}"

    [storage bar]
    type = "filesystem"
    fileext = ".txt"
    path = "{bar}"

    [pair foobar]
    a = "foo"
    b = "bar"
    collections = null
    ''').format(foo=str(foo), bar=str(bar)))

    result = runner.invoke(['discover'], input='y\n' * 2)
    assert not result.exception

    result = runner.invoke(['sync'])
    assert not result.exception

    assert foo.exists()
    assert bar.exists()


def test_null_collection_with_named_collection(tmpdir, runner):
    runner.write_with_general(dedent('''
    [pair foobar]
    a = "foo"
    b = "bar"
    collections = [["baz", "baz", null]]

    [storage foo]
    type = "filesystem"
    path = "{base}/foo/"
    fileext = ".txt"

    [storage bar]
    type = "singlefile"
    path = "{base}/bar.txt"
    '''.format(base=str(tmpdir))))

    result = runner.invoke(['discover'], input='y\n' * 2)
    assert not result.exception

    foo = tmpdir.join('foo')
    foobaz = foo.join('baz')
    assert foo.exists()
    assert foobaz.exists()

    bar = tmpdir.join('bar.txt')
    assert bar.exists()

    foobaz.join('lol.txt').write('BEGIN:VCARD\nUID:HAHA\nEND:VCARD')

    result = runner.invoke(['sync'])
    assert not result.exception

    assert 'HAHA' in bar.read()


@given(a_requires=st.booleans(), b_requires=st.booleans())
@settings(suppress_health_check=["HealthCheck.too_slow"])
def test_collection_required(a_requires, b_requires, tmpdir, runner,
                             monkeypatch):

    class TestStorage(Storage):
        storage_name = 'test'

        def __init__(self, require_collection, **kw):
            if require_collection:
                assert not kw.get('collection')
                raise exceptions.CollectionRequired()

    from vdirsyncer.cli.utils import storage_names
    monkeypatch.setitem(storage_names._storages, 'test', TestStorage)

    runner.write_with_general(dedent('''
    [pair foobar]
    a = "foo"
    b = "bar"
    collections = null

    [storage foo]
    type = "test"
    require_collection = {a}

    [storage bar]
    type = "test"
    require_collection = {b}
    '''.format(a=json.dumps(a_requires), b=json.dumps(b_requires))))

    result = runner.invoke(['discover'])
    if a_requires or b_requires:
        assert result.exception
        assert \
            'One or more storages don\'t support `collections = null`.' in \
            result.output
