# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import contextlib
import copy
import hashlib
import json
import logging
import mock
import os
import os.path
import shutil
import sys
import tempfile
import threading
import tooltool
import unittest

from io import StringIO, BytesIO
from io import open
from nose.tools import eq_

PY3 = sys.version_info[0] == 3
CWD_PATH = os.getcwd()

from tooltool import to_binary, get_hexdigest, open_attrs, URLError, HTTPError

if PY3:
    urlopen_module_as_str = 'urllib.request.urlopen'
    import http.server as BaseHTTPServer
else:
    urlopen_module_as_str = 'urllib2.urlopen'
    import BaseHTTPServer


class TestDirMixin(object):

    def setUpTestDir(self):
        self.test_dir = os.path.abspath(tempfile.mkdtemp())
        self.__old_cwd = os.getcwd()
        if os.path.exists(self.test_dir):
            shutil.rmtree(self.test_dir)
        os.makedirs(self.test_dir)
        os.chdir(self.test_dir)

    def tearDownTestDir(self):
        os.chdir(self.__old_cwd)
        shutil.rmtree(self.test_dir)


class BufferHandler(logging.Handler):

    def __init__(self, buffer):
        self.buffer = buffer
        logging.Handler.__init__(self)

    def emit(self, record):
        self.buffer.append((record.levelno, record.getMessage()))

    @classmethod
    @contextlib.contextmanager
    def capture(cls, logger_name):
        logger = logging.getLogger(logger_name)
        buffer = []
        handler = cls(buffer)
        logger.addHandler(handler)
        try:
            yield buffer
        finally:
            logger.removeHandler(handler)


class DigestTests(unittest.TestCase):

    def setUp(self):
        self.sample_data = open('tes
t_file.ogg', 'rb')
        self.sample_algo = 'sha1'
        self.sample_digest = 'de3e3bbffd83c328ad7d9537ad2d03f68fc02e52'

    def test_digest_file(self):
        test_digest = tooltool.digest_file(self.sample_data, self.sample_algo)
        # If this assertion fails, verify that 
test_file.ogg is an ogg file
        # of Linus Torvalds explaining how he pronounces 'Linux'
        self.assertEqual(test_digest, self.sample_digest)


class BaseFileRecordTest(unittest.TestCase):

    def setUp(self):
        self.sample_file = 'test_file.ogg'
        self.sample_file_path = os.path.join(CWD_PATH, self.sample_file)
        self.sample_algo = 'sha512'
        self.sample_size = os.path.getsize(self.sample_file_path)
        with open(self.sample_file_path, 'rb') as f:
            self.sample_hash = tooltool.digest_file(f, self.sample_algo)
        self.test_record = tooltool.FileRecord(
            filename=self.sample_file,
            size=self.sample_size,
            digest=self.sample_hash,
            algorithm=self.sample_algo
        )
        self.test_record_json = {
            'filename': self.sample_file,
            'algorithm': self.sample_algo,
            'digest': self.sample_hash,
            'size': self.sample_size,
        }
        # using mkstemp to ensure that the filename generated
        # isn't actually on the system.
        (tmpfd, filename) = tempfile.mkstemp()
        os.close(tmpfd)
        os.remove(filename)
        if os.path.exists(filename):
            self.fail('did not remove %s' % filename)
        self.absent_file = filename


class BaseFileRecordListTest(BaseFileRecordTest):

    def setUp(self):
        BaseFileRecordTest.setUp(self)
        self.record_list = []
        for i in range(0, 4):
            record = copy.deepcopy(self.test_record)
            record.algorithm = i
            self.record_list.append(record)


class BaseManifestTest(TestDirMixin, BaseFileRecordTest):

    def setUp(self):
        BaseFileRecordTest.setUp(self)
        self.setUpTestDir()
        self.sample_manifest = tooltool.Manifest([self.test_record])
        self.sample_manifest_file = 'manifest.tt'
        with open(self.sample_manifest_file, **open_attrs) as tmpfile:
            self.sample_manifest.dump(tmpfile, fmt='json')

    def tearDown(self):
        self.tearDownTestDir()


class TestFileRecord(BaseFileRecordTest):

    def test_create_with_posix_path_info(self):
        self.assertRaises(tooltool.ExceptionWithFilename, lambda:
                          tooltool.FileRecord('abc/def', 10, 'abcd', 'alpha'))

    def test_create_with_windows_path_info(self):
        self.assertRaises(tooltool.ExceptionWithFilename, lambda:
                          tooltool.FileRecord(r'abc\def', 10, 'abcd', 'alpha'))

    def test_present(self):
        # this test feels silly, but things are built on this
        # method, so probably best to test it
        self.assertTrue(self.test_record.present())

    def test_absent(self):
        self.test_record.filename = self.absent_file
        self.assertFalse(self.test_record.present())

    def test_valid_size(self):
        self.assertTrue(self.test_record.validate_size())

    def test_invalid_size(self):
        self.test_record.size += 1
        self.assertFalse(self.test_record.validate_size())

    def test_size_of_missing_file(self):
        self.test_record.filename = self.absent_file
        self.assertRaises(
            tooltool.MissingFileException, self.test_record.validate_size)

    def test_valid_digest(self):
        self.assertTrue(self.test_record.validate_digest())

    def test_invalid_digest(self):
        self.test_record.digest = 'NotValidDigest'
        self.assertFalse(self.test_record.validate_digest())

    def test_digest_of_missing_file(self):
        self.test_record.filename = self.absent_file
        self.assertRaises(
            tooltool.MissingFileException, self.test_record.validate_digest)

    def test_overall_valid(self):
        self.assertTrue(self.test_record.validate())

    def test_overall_invalid_size(self):
        self.test_record.size = 3
        self.assertFalse(self.test_record.validate())

    def test_overall_invalid_digest(self):
        self.test_record.digest = 'NotValidDigest'
        self.assertFalse(self.test_record.validate())

    def test_overall_invalid_missing_file(self):
        self.test_record.filename = self.absent_file
        self.assertRaises(
            tooltool.MissingFileException, self.test_record.validate)

    def test_equality(self):
        test_record2 = copy.deepcopy(self.test_record)
        self.assertEqual(self.test_record, test_record2)
        self.assertEqual(self.test_record, self.test_record)

    def test_equality_validity(self):
        test_record2 = copy.deepcopy(self.test_record)
        self.test_record.visibility = True
        test_record2.visibility = False
        self.assertNotEqual(self.test_record, test_record2)

    def test_inequality(self):
        for i in ['filename', 'size', 'algorithm', 'digest', 'version']:
            test_record2 = copy.deepcopy(self.test_record)
            test_record2.__dict__[i] = 'wrong!'
            self.assertNotEqual(self.test_record, test_record2)

    def test_repr(self):
        a = eval(repr(self.test_record))
        self.assertEqual(str(a), str(self.test_record))
        self.assertEqual(a, self.test_record)

    def test_create_file_record(self):
        fr = tooltool.create_file_record(self.sample_file, self.sample_algo)
        self.assertEqual(self.test_record, fr)

    def test_describe_absent(self):
        self.test_record.filename = self.absent_file
        self.assertEqual("'%s' is absent" %
                         self.absent_file, self.test_record.describe())

    def test_describe_present_valid(self):
        self.assertEqual("'%s' is present and valid" % self.test_record.filename,
                         self.test_record.describe())

    def test_describe_present_invalid(self):
        self.test_record.size = 4
        self.test_record.digest = 'NotValidDigest'
        self.assertEqual("'%s' is present and invalid" % self.test_record.filename,
                         self.test_record.describe())


class TestFileRecordJSONCodecs(BaseFileRecordListTest):

    def test_default(self):
     