import os
import sys
import random
import time
import string
import json
import math
import unittest
import warnings
import tempfile
from pathlib import Path
import socket

sys.path.insert(0, str(Path(__file__).parent.parent))

import pySmartDL

class TestSmartDL(unittest.TestCase):
    def setUp(self):
        warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
        self.dl_dir = os.path.join(tempfile.gettempdir(), "".join([random.choice(string.ascii_letters+string.digits) for i in range(8)]), '')
        while os.path.exists(self.dl_dir):
            self.dl_dir = os.path.join(tempfile.gettempdir(), "".join([random.choice(string.ascii_letters+string.digits) for i in range(8)]), '')
            
        self.res_7za920_mirrors = [
            "https://github.com/iTaybb/pySmartDL/raw/master/test/7za920.zip",
            "https://sourceforge.mirrorservice.org/s/se/sevenzip/7-Zip/9.20/7za920.zip",
            "http://www.bevc.net/dl/7za920.zip",
            "http://ftp.psu.ru/tools/7-zip/stable/7za920.zip",
            "http://www.mirrorservice.org/sites/downloads.sourceforge.net/s/se/sevenzip/7-Zip/9.20/7za920.zip"
        ]
        self.res_7za920_hash = '2a3afe19c180f8373fa02ff00254d5394fec0349f5804e0ad2f6067854ff28ac'
        self.res_testfile_1gb = 'http://www.ovh.net/files/1Gio.dat'
        self.res_testfile_100mb = 'http://www.ovh.net/files/100Mio.dat'
        self.enable_logging = "-vvv" in sys.argv

    def test_dependencies(self):
        self.assertTrue(sys.version_info >= (3, 4))
    
    def test_download(self):
        obj = pySmartDL.SmartDL(self.res_7za920_mirrors, dest=self.dl_dir, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.start()
        self.assertEqual(obj.get_progress_bar(), '[##################]')

        data = obj.get_data(binary=True, bytes=2)
        
        self.assertEqual(data, b'PK')

        # attempt to start a completed task
        with self.assertRaises(RuntimeError) as ctx:
            obj.start()
    
    def test_mirrors(self):
        urls = ["http://totally_fake_website/7za.zip", "https://github.com/iTaybb/pySmartDL/raw/master/test/7za920.zip"]
        obj = pySmartDL.SmartDL(urls, dest=self.dl_dir, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.start()
        
        self.assertTrue(obj.isSuccessful())
        
    def test_hash(self):
        obj = pySmartDL.SmartDL(self.res_7za920_mirrors, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.add_hash_verification('sha256' , self.res_7za920_hash)  # good hash
        obj.start(blocking=False)  # no exceptions
        obj.wait()
        
        self.assertTrue(obj.isSuccessful())
        
        obj = pySmartDL.SmartDL(self.res_7za920_mirrors, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.add_hash_verification('sha256' ,'a'*64)  # bad hash
        obj.start(blocking=False)  # no exceptions
        obj.wait()
        
        self.assertFalse(obj.isSuccessful())
        self.assertTrue(any([isinstance(e, pySmartDL.HashFailedException) for e in obj.get_errors()]))
        
    def test_pause_unpause(self, testfile=None):
        obj = pySmartDL.SmartDL(testfile if testfile else self.res_7za920_mirrors, dest=self.dl_dir, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.start(blocking=False)
        
        while not obj.get_dl_size():
            time.sleep(0.1)
        
        # pause
        obj.pause()
        time.sleep(0.5)
        if obj.get_status() == "finished":
            # too bad, the file was too small and was downloaded complectely until we stopped it.
            # We should download a bigger file
            if self.res_testfile_100mb == testfile:
                self.fail("The download got completed before we could stop it, even though we've used a big file. Are we on a 100GB/s internet connection or somethin'?")
            return self.test_pause_unpause(testfile=self.res_testfile_100mb)
        
        dl_size = obj.get_dl_size()

        # verify download has really stopped
        time.sleep(2.5)
        self.assertEqual(dl_size, obj.get_dl_size())
        
        # continue
        obj.unpause()
        time.sleep(2.5)
        self.assertNotEqual(dl_size, obj.get_dl_size())
        
        obj.wait()
        self.assertTrue(obj.isSuccessful())

    def test_stop(self):
        obj = pySmartDL.SmartDL(self.res_testfile_100mb, dest=self.dl_dir, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.start(blocking=False)

        while not obj.get_dl_size():
            time.sleep(0.1)

        obj.stop()
        obj.wait()
        self.assertFalse(obj.isSuccessful())

    def test_speed_limiting(self):
        obj = pySmartDL.SmartDL(self.res_testfile_1gb, dest=self.dl_dir, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.limit_speed(1024**2)  # 1MB per sec
        obj.start(blocking=False)

        while not obj.get_dl_size():
            time.sleep(0.1)
        time.sleep(30)

        expected_dl_size = 30 * 1024**2
        allowed_delta = 0.6  # because we took only 30sec, the delta needs to be quite big, it we were to test 60sec the delta would probably be much smaller
        diff = math.fabs(expected_dl_size - obj.get_dl_size()) / expected_dl_size

        obj.stop()
        obj.wait()

        self.assertLessEqual(diff, allowed_delta)
    
    def test_basic_auth(self):
        basic_auth_test_url = "https://httpbin.org/basic-auth/user/passwd"
        obj = pySmartDL.SmartDL(basic_auth_test_url, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.add_basic_authentication('user', 'passwd')
        obj.start()
        data = obj.get_data()
        self.assertTrue(json.loads(data)['authenticated'])
        
    def test_unicode(self):
        url = "https://he.wikipedia.org/wiki/ג'חנון"
        obj = pySmartDL.SmartDL(url, progress_bar=False, connect_default_logger=self.enable_logging)
        obj.start()

    def test_timeout(self):
        self.assertRaises(socket.timeout, pySmartDL.SmartDL, "https://httpbin.org/delay/10", progress_bar=False, timeout=3, connect_default_logger=self.enable_logging)

        obj = pySmartDL.SmartDL("https://httpbin.org/delay/3", progress_bar=False, timeout=15, connect_default_logger=self.enable_logging)
        obj.start(blocking=False)
        obj.wait()
        self.assertTrue(obj.isSuccessful())

    def test_custom_headers(self):
        # sending custom user agent
        ua = "pySmartDL/1.3.2"
       	request_args = {"headers": {"User-Agent": ua}}
        obj = pySmartDL.SmartDL("http://httpbin.org/headers", request_args=request_args, progress_bar=False)
        obj.start()
        data = obj.get_json()
        self.assertTrue(data['headers']['User-Agent'] == ua)

        # passing empty request_args
        obj = pySmartDL.SmartDL("http://httpbin.org/headers", request_args={}, progress_bar=False)
        obj.start()
        self.assertTrue(obj.isSuccessful())

    def test_utils(self):
        self.assertEqual(pySmartDL.utils.progress_bar(0.6, length=42), '[########################----------------]')
        self.assertEqual(pySmartDL.utils.sizeof_human(175799789), '167.7 MB')
        self.assertEqual(pySmartDL.utils.sizeof_human(0), '0 bytes')
        self.assertEqual(pySmartDL.utils.time_human(50), '50 seconds')
        self.assertEqual(pySmartDL.utils.time_human(50, fmt_short=True), '50s')
        self.assertEqual(pySmartDL.utils.time_human(0, fmt_short=True), '0s')
        self._test_calc_chunk_size(10000, 10, 20)
        self._test_calc_chunk_size(1906023034, 20, 20)
        self._test_calc_chunk_size(261969919, 20, 32)

    def _test_calc_chunk_size(self, filesize, threads, minChunkFile):
        chunks = pySmartDL.utils.calc_chunk_size(filesize, threads, 20)
        self.assertEqual(chunks[0][0], 0)
        self.assertIsInstance(chunks[0][0], int)
        self.assertIsInstance(chunks[0][1], int)

        for i in range(1, len(chunks)):
            self.assertIsInstance(chunks[i][0], int)
            self.assertIsInstance(chunks[i][1], int)
            self.assertTrue(chunks[i][0] <= chunks[i][1])
            self.assertEqual(chunks[i-1][1] + 1, chunks[i][0])
            
        self.assertEqual(chunks[-1][1], filesize-1)

        
def test_suite():
    suite = unittest.makeSuite(TestSmartDL)
    return suite

if __name__ == '__main__':
    unittest.main(verbosity=2)