#! /usr/bin/python3
# -*- coding: utf-8 -*-

##############################################################################
#    Copyright (C) 2021-current alexpdev
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
##############################################################################
"""
Testing functions for the progress module.
"""

import os
import sys
from pathlib import Path

from tests import (
    dir1, dir2, file1, file2, filemeta1, filemeta2, metafile1, metafile2,
    rmpath, sizedfiles, sizes)
from torrentfile.cli import main_script as main
from torrentfile.recheck import Checker
from torrentfile.utils import ArgumentError


def test_fixtures():
    """
    Test fixtures exist.
    """
    assert dir1 and dir2 and file1 and file2
    assert filemeta1 and filemeta2 and metafile1
    assert metafile2 and sizes and sizedfiles


def test_checker_class(dir1, metafile1):
    """
    Test Checker Class against meta files.
    """
    checker = Checker(metafile1, dir1)
    assert checker.results() == 100


def test_checker_first_piece(dir2, sizedfiles):
    """
    Test Checker Class when first piece is slightly alterred.
    """

    def change(path):
        """
        Change some bytes in file.
        """
        if path.is_file():
            new = b"Something other than what was there before."
            with open(path, "rb") as bfile:
                data = bfile.read()
            new_len = len(new)
            content = b"".join([new, data[new_len:]])
            with open(path, "wb") as bdoc:
                bdoc.write(content)
        elif path.is_dir():
            for item in path.iterdir():
                change(item)

    change(Path(dir2))
    checker = Checker(sizedfiles, dir2)
    assert checker.results() != 100


def test_checker_first_piece_alt(dir2, sizedfiles):
    """
    Test Checker Class when first piece is slightly alterred.
    """

    def change(path):
        """
        Change some bytes in file.
        """
        if os.path.isfile(path):
            with open(path, "rb") as bfile:
                data = bfile.read()
            new = b"some_other_bytes_to_use"
            new_len = len(new)
            with open(path, "wb") as wfile:
                wfile.write(new + data[new_len:])
        elif os.path.isdir(path):
            for item in os.listdir(path):
                change(os.path.join(path, item))

    change(dir2)
    checker = Checker(sizedfiles, dir2)
    assert checker.results() != 100


def test_partial_metafiles(dir2, sizedfiles):
    """
    Test Checker with data that is expected to be incomplete.
    """

    def shortenfile(path):
        """
        Shorten a few files for testing purposes.
        """
        with open(path, "rb") as bfile:
            data = bfile.read()
        with open(path, "wb") as bfile:
            bfile.write(data[:-(2**10)])

    for item in os.listdir(dir2):
        full = os.path.join(dir2, item)
        if os.path.isfile(full):
            shortenfile(full)

    testdir = os.path.dirname(dir2)
    checker = Checker(sizedfiles, testdir)
    assert checker.results() != 100


def test_checker_callback(dir1, metafile1):
    """
    Test Checker class with directory that points to nothing.
    """
    Checker.register_callback(lambda *x: print(x))
    checker = Checker(metafile1, str(dir1))
    assert checker.results() == 100


def test_checker_cli_args(dir1, metafile1):
    """
    Test exclusive Checker Mode CLI.
    """
    sys.argv = ["torrentfile", "check", str(metafile1), str(dir1)]
    output = main()
    assert output == 100


def test_checker_parent_dir(dir1, metafile1):
    """
    Test providing the parent directory for torrent checking feature.
    """
    checker = Checker(metafile1, os.path.dirname(dir1))
    assert checker.results() == 100


def test_checker_with_file(file1, filemeta1):
    """
    Test checker with single file torrent.
    """
    checker = Checker(filemeta1, file1)
    assert checker.results() == 100


def test_checker_no_meta_file():
    """
    Test Checker when incorrect metafile is provided.
    """
    try:
        Checker("peaches", "$")
    except FileNotFoundError:
        assert True


def test_checker_wrong_root_dir(metafile1):
    """
    Test Checker when incorrect root directory is provided.
    """
    try:
        Checker(metafile1, "fake")
    except FileNotFoundError:
        assert True


def test_checker_missing(sizedfiles, dir2):
    """
    Test Checker class when files are missing from contents.
    """
    count = 0
    for fd in Path(dir2).iterdir():
        if fd.is_file() and count < 2:
            rmpath(fd)
    checker = Checker(sizedfiles, dir2)
    assert int(checker.results()) < 100


def test_checker_class_allfiles(sizedfiles, dir2):
    """
    Test Checker class when all files are missing from contents.
    """

    def traverse(path):
        """
        Traverse internal subdirectories.
        """
        if path.is_file():
            rmpath(path)
        elif path.is_dir():
            for item in path.iterdir():
                traverse(item)

    traverse(dir2)
    checker = Checker(sizedfiles, dir2)
    assert int(checker.results()) < 100


def test_checker_class_allpaths(sizedfiles, dir2):
    """
    Test Checker class when all files are missing from contents.
    """
    for item in Path(str(dir2)).iterdir():
        rmpath(item)
    checker = Checker(sizedfiles, dir2)
    assert int(checker.results()) < 100


def test_checker_class_half_file(filemeta2, file2):
    """
    Test Checker class with half size single file.
    """
    half = int(os.path.getsize(file2) / 2)
    barr = bytearray(half)
    with open(file2, "rb") as content:
        content.readinto(barr)
    with open(file2, "wb") as content:
        content.write(barr)
    checker = Checker(filemeta2, file2)
    assert int(checker.results()) != 10


def test_checker_missing_singles(dir2, sizedfiles):
    """
    Test Checker class with half size single file.
    """

    def walk(root):
        """
        Remove first file found.
        """
        if root.is_file():
            rmpath(root)
            return True
        if root.is_dir():
            for item in root.iterdir():
                walk(item)
        return False

    walk(Path(dir2))
    checker = Checker(sizedfiles, dir2)
    assert int(checker.results()) < 100


def test_checker_result_property(dir1, metafile1):
    """
    Test Checker class with half size single file.
    """
    checker = Checker(metafile1, dir1)
    result = checker.results()
    assert checker.results() == result


def test_checker_simplest(dir1, metafile1):
    """
    Test the simplest example.
    """
    checker = Checker(metafile1, dir1)
    assert checker.results() == 100


def test_checker_empty_files(dir2, sizedfiles):
    """
    Test Checker when directory contains 0 length files.
    """

    def empty_files(root):
        """
        Dump contents of files.
        """
        if os.path.isfile(root):
            with open(root, "wb") as _:
                pass
            assert os.path.getsize(root) == 0
        elif os.path.isdir(root):
            for item in os.listdir(root):
                return empty_files(os.path.join(root, item))
        return root

    empty_files(dir2)
    checker = Checker(sizedfiles, dir2)
    assert checker.results() != 100


def test_recheck_wrong_dir(metafile1):
    """
    Test recheck function with directory that doesn't contain the contents.
    """
    grandparent = os.path.dirname(os.path.dirname(metafile1))
    try:
        _ = Checker(metafile1, grandparent)
    except FileNotFoundError:
        assert True


def test_recheck_mismatch_args(metafile1):
    """
    Test recheck function with mismatched directory.
    """
    grandparent = os.path.dirname(os.path.dirname(metafile1))
    try:
        _ = Checker(grandparent, metafile1)
    except ArgumentError:
        assert True
