import os
import ast
from itertools import filterfalse

import git

from .utils.url_helpers import get_filename_from_path


class ParsedPyFile:

    def __init__(self, path, content):
        self.path = path
        self.content = content
        self.name = get_filename_from_path(path)
        self.ast_tree = self._generate_ast_tree(content)

    @staticmethod
    def _generate_ast_tree(content):
        try:
            tree = ast.parse(content)
        except SyntaxError:
            tree = None
        else:
            ParsedPyFile._set_parents(tree)
        return tree

    @staticmethod
    def _set_parents(tree):
        for node in ast.walk(tree):
            for child in ast.iter_child_nodes(node):
                child.parent = node

    def is_in_whitelist(self, whitelist):
        for whitelisted_part in whitelist:
            if whitelisted_part in self.path:
                return True
        return False

    @property
    def is_syntax_correct(self):
        return self.ast_tree is not None

    def get_name_with_line(self, line_number):
        return '{}:{}'.format(self.name, line_number)

    def __str__(self):
        return self.name

    def __repr__(self):
        return 'ParsedPyFile object for the file {}'.format(self.name)


class LocalRepository:

    def __init__(self, repository_path):
        self._repo = git.Repo(repository_path)

    def count_commits(self):
        return len(list(self._repo.iter_commits()))

    def iter_commits(self, *args, **kwargs):
        return self._repo.iter_commits(*args, **kwargs)

    def is_tracked_directory(self, directory):
        # https://stackoverflow.com/a/34329915/3694363
        return bool(self._repo.git.ls_files(directory))


class ProjectFolder:

    def __init__(self, path, directories_to_skip=None):
        if not os.path.isdir(path):
            raise FileNotFoundError('Path "{}" not found or is not a directory.'.format(path))
        self.path = path
        self._parsed_py_files = self._get_parsed_py_files(directories_to_skip)
        try:
            self.repo = LocalRepository(path)
        except git.InvalidGitRepositoryError:
            self.repo = None

    def does_file_exist(self, filename):
        return os.path.isfile(os.path.join(self.path, filename))

    @staticmethod
    def _make_root_relative_to_path(root, path):
        return root[len(path) + 1:]  # +1 is for the slash

    def enumerate_directories(self):
        for root, folders, _ in os.walk(self.path):
            relative_root = self._make_root_relative_to_path(root, self.path) or '.'
            for folder in folders:
                directory = '{root}{sep}{folder}'.format(
                    root=relative_root,
                    sep=os.path.sep,
                    folder=folder
                )
                yield directory

    def get_source_file_contents(self, extension_list, directories_to_skip=None):
        file_paths = []
        file_contents = []
        directories_to_skip = directories_to_skip or []
        for dirname, directories_list, filenames in os.walk(self.path, topdown=True):
            directories_list[:] = [
                d for d in directories_list
                if d not in directories_to_skip
            ]
            for filename in filenames:
                extension = os.path.splitext(filename)[1]
                if extension in extension_list:
                    file_paths.append(os.path.join(dirname, filename))
        for file_path in file_paths:
            with open(file_path, 'r', encoding='utf-8') as file_handler:
                file_contents.append(file_handler.read())
        source_file_contents = list(zip(file_paths, file_contents))
        return source_file_contents

    def _get_parsed_py_files(self, directories_to_skip=None):
        py_files = self.get_source_file_contents(['.py'], directories_to_skip) or [(), ()]
        parsed_py_files = [ParsedPyFile(path, content) for path, content in py_files]
        return parsed_py_files

    def get_parsed_py_files(self, whitelist=None):
        parsed_py_files = self._parsed_py_files
        if whitelist:
            parsed_py_files = filterfalse(
                lambda parsed_file: parsed_file.is_in_whitelist(whitelist),
                parsed_py_files
            )
        return parsed_py_files

    def get_file(self, filename):
        for dirname, _, files in os.walk(self.path, topdown=True):
            for file in files:
                if file == filename:
                    with open(os.path.join(dirname, file), encoding='utf-8') as file_handler:
                        return file_handler.read()

    def does_directory_exist(self, dirname_to_find):
        for dirname, dirs, _ in os.walk(self.path, topdown=True):
            if dirname == dirname_to_find or dirname_to_find in dirs:
                return True
        return False