import os
from functools import partial

import pytest
from pkgcore.fetch import base, errors, fetchable
from snakeoil import data_source
from snakeoil.chksum import get_handlers

repeating_str = 'asdf'
data = repeating_str * 4000
handlers = get_handlers()


from snakeoil.mappings import LazyValDict


def _callback(chf):
    return handlers[chf](data_source.data_source(data))
chksums = LazyValDict(frozenset(handlers.keys()), _callback)

# get a non size based chksum
known_chksum = [x for x in handlers.keys() if x != "size"][0]

class TestFetcher:

    @pytest.fixture(autouse=True)
    def _setup(self, tmpdir):
        self.fp = os.path.join(str(tmpdir), "test")
        self.obj = fetchable(self.fp, chksums=chksums)
        self.fetcher = base.fetcher()

    def write_data(self, data=data):
        with open(self.fp, "w") as f:
            f.write(data)

    def test__call__(self):
        l = []
        class c(base.fetcher):
            def fetch(self, *a, **kw):
                l.extend((a, kw))

        o = c()
        o.fetch(1, foon=True)
        assert [(1,), {"foon": True}] == l
        self.write_data()
        assert self.fetcher._verify(self.fp, self.obj) == None
        self.write_data("asdf")
        with pytest.raises(errors.FetchError) as excinfo:
            self.fetcher._verify(self.fp, self.obj)
        assert excinfo.value.resumable

    def test_verify_all_chksums(self):
        self.write_data()
        subhandlers = dict([list(handlers.items())[0]])
        with pytest.raises(errors.RequiredChksumDataMissing):
            self.fetcher._verify(self.fp, self.obj, handlers=subhandlers)
        self.fetcher._verify(self.fp, self.obj)
        assert None == self.fetcher._verify(
            self.fp, self.obj, handlers=subhandlers, all_chksums=False)

    def test_size_verification_first(self):
        self.write_data()
        chksum_data = dict(chksums.items())
        l = []
        def f(chf, fp):
            l.append(chf)
            return chksum_data[chf]
        subhandlers = {"size": partial(f, 'size'), known_chksum:partial(f, known_chksum)}

        # exact size verification
        self.fetcher._verify(self.fp, self.obj, handlers=subhandlers, all_chksums=False)
        assert ['size', known_chksum] == l
        for x in (-100, 100):
            while l:
                l.pop(-1)
            chksum_data["size"] = chksums["size"] + x
            if x > 0:
                with pytest.raises(errors.ChksumFailure) as excinfo:
                    self.fetcher._verify(
                        self.fp, self.obj, handlers=subhandlers, all_chksums=False)
                assert excinfo.value.chksum == 'size'
            else:
                with pytest.raises(errors.FetchError) as excinfo:
                    self.fetcher._verify(
                        self.fp, self.obj, handlers=subhandlers, all_chksums=False)
                assert excinfo.value.resumable
            assert ['size'] == l

    def test_normal(self):
        self.write_data()
        assert self.fetcher._verify(self.fp, self.obj) == None
        self.write_data(data[:-1])
        with pytest.raises(errors.FetchError) as excinfo:
            self.fetcher._verify(self.fp, self.obj)
        assert excinfo.value.resumable
        # verify it returns -2 for missing file paths.
        os.unlink(self.fp)
        with pytest.raises(errors.MissingDistfile) as excinfo:
            self.fetcher._verify(self.fp, self.obj)
        assert excinfo.value.resumable
        self.write_data(data + "foon")
        with pytest.raises(errors.ChksumFailure) as excinfo:
            self.fetcher._verify(self.fp, self.obj)
        assert excinfo.value.chksum == 'size'

        # verify they're ran one, and only once
        l = []
        def f(chf, fp):
            l.append(chf)
            return chksums[chf]

        alt_handlers = {chf: partial(f, chf) for chf in chksums}
        assert None == self.fetcher._verify(self.fp, self.obj, handlers=alt_handlers)
        assert sorted(l) == sorted(alt_handlers)
