#!/usr/bin/env python

import argparse
import itertools
import logging
import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from deplint.analyzers.can_be_upgraded import CanBeUpgradedAnalyzer
from deplint.analyzers.is_transitive_dep import IsTransitiveDepAnalyzer
from deplint.analyzers.is_unused import IsUnusedAnalyzer
from deplint.analyzers.is_vulnerable import IsVulnerableAnalyzer
from deplint.analyzers.required_installed import RequiredInstalledAnalyzer
from deplint.analyzers.required_not_installed import RequiredNotInstalledAnalyzer
from deplint.data_sources.pip import Pip
from deplint.data_sources.site_packages.site_packages import SitePackages
from deplint.data_sources.source_code.git_grep import GitGrep
from deplint.parsers.requirements_txt import RequirementsTxtParser
from deplint.ui.writer import UiWriter


_logger = logging.getLogger(__name__)


class RequirementsLinter(object):
    def __init__(self, args):
        self.args = args

        self.ui = UiWriter(verbose=args.verbose)
        self.pip = Pip(ui=self.ui, python_path=self.args.python_path)

        self.grep = GitGrep(basedir=args.basedir)

    def analyze_installed(self):
        # gather all the data we need to analyze
        parser = RequirementsTxtParser(fileobj=open(self.args.requirements_txt))
        requirements_txt = parser.parse()

        installed_packages = self.pip.list_installed_packages()

        # run the analyzers
        analyzer = RequiredInstalledAnalyzer(
            requirements_txt=requirements_txt,
            installed_packages=installed_packages,
        )
        advice_list1 = analyzer.analyze()

        analyzer = RequiredNotInstalledAnalyzer(
            requirements_txt=requirements_txt,
            installed_packages=installed_packages,
        )
        advice_list2 = analyzer.analyze()

        return [advice_list1, advice_list2]

    def analyze_tracked(self):
        # gather all the data we need to analyze
        parser = RequirementsTxtParser(fileobj=open(self.args.requirements_txt))
        requirements_txt = parser.parse()

        installed_packages = self.pip.list_installed_packages()

        installed_names = [pkg.name for pkg in installed_packages.packages]
        site_packages = SitePackages(
            python_path=args.python_path,
            installed_package_names=installed_names,
        )

        # run the analyzers
        analyzer = IsTransitiveDepAnalyzer(
            requirements_txt=requirements_txt,
            installed_packages=installed_packages,
            site_packages=site_packages,
        )
        advice_list = analyzer.analyze()

        return [advice_list]

    def analyze_upgrade(self):
        # gather all the data we need to analyze
        parser = RequirementsTxtParser(fileobj=open(self.args.requirements_txt))
        requirements_txt = parser.parse()

        installed_packages = self.pip.list_installed_packages()

        package_names = [pkg.name for pkg in requirements_txt.packages]
        available_packages = self.pip.search_for_packages(package_names)

        # run the analyzers
        analyzer = CanBeUpgradedAnalyzer(
            requirements_txt=requirements_txt,
            installed_packages=installed_packages,
            available_packages=available_packages,
        )
        advice_list = analyzer.analyze()

        return [advice_list]

    def analyze_unused(self):
        # gather all the data we need to analyze
        parser = RequirementsTxtParser(fileobj=open(self.args.requirements_txt))
        requirements_txt = parser.parse()

        installed_packages = self.pip.list_installed_packages()

        installed_names = [pkg.name for pkg in installed_packages.packages]
        site_packages = SitePackages(
            python_path=args.python_path,
            installed_package_names=installed_names,
        )

        # run the analyzers
        analyzer = IsUnusedAnalyzer(
            requirements_txt=requirements_txt,
            installed_packages=installed_packages,
            site_packages=site_packages,
            grep=self.grep,
        )
        advice_list = analyzer.analyze()

        return [advice_list]

    def analyze_vulnerable(self):
        # gather all the data we need to analyze
        installed_packages = self.pip.list_installed_packages()

        # run the analyzers
        analyzer = IsVulnerableAnalyzer(
            installed_packages=installed_packages,
        )
        advice_list = analyzer.analyze()

        return [advice_list]

    def run(self):
        self.ui.inform('Using python: %s' % self.args.python_path)
        self.ui.inform('Using pip: %s' % self.pip.pip_path)
        self.ui.inform('Using grep: %s' % self.grep.as_display_name())
        self.ui.inform('Using basedir: %s' % self.args.basedir)
        self.ui.inform('Using requirements.txt: %s' % self.args.requirements_txt)

        advice_lists = []
        if self.args.action == 'installed':
            advice_lists = self.analyze_installed()

        elif self.args.action == 'tracked':
            advice_lists = self.analyze_tracked()

        elif self.args.action == 'upgrade':
            advice_lists = self.analyze_upgrade()

        elif self.args.action == 'unused':
            advice_lists = self.analyze_unused()

        elif self.args.action == 'vulnerable':
            advice_lists = self.analyze_vulnerable()

        advice_all = itertools.chain(
            *(ad_list.advice_list for ad_list in advice_lists)
        )

        # display all advice
        for advice in advice_all:
            self.ui.output(advice.format_display_line())

        # if we have any warnings or errors then exit with error
        if any((ad_list.has_problems() for ad_list in advice_lists)):
            return 1

        return 0


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description=(
            'Linter for dependencies.\n'
            '\n'
            '\n'
            '\n'
            'Are all requirements installed to the correct version?\n'
            '$ deplint installed\n'
            '\n'
            'You should generally fix any warnings/errors from the installed check first,\n'
            'as the other checks depend on metadata from installed dependencies\n'
            'to work correctly.\n'
            '\n'
            'Are any of the requirements transitive dependencies and can be dropped?\n'
            '$ deplint tracked\n'
            '\n'
            'Are any of the requirements not used in the code?\n'
            '$ deplint unused\n'
            '\n'
            'Can any of the requirements be upgraded?\n'
            '$ deplint upgrade\n'
            '\n'
            'Do any of the requirements have known security vulnerabilities?\n'
            '$ deplint vulnerable\n'
            '\n'
            '\n'
            'By default deplint assumes you are running against the current\n'
            'virtualenv. If you want to run against another virtualenv (eg. \n'
            'you have deplint installed system wide) you need to provide both \n'
            '-r|--requirements and -p|--python flags, like so:\n'
            '$ deplint installed \\\n'
            '    -r ~/src/project/requirements.txt \\\n'
            '    -p ~/.virtualenvs/project/bin/python'
        ),
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument('action',
                        choices=('installed', 'tracked', 'upgrade', 'unused', 'vulnerable'),
                        help='Action to take')
    parser.add_argument('-b', '--basedir', dest='basedir', metavar='<basedir>',
                        help='The base directory of the project')
    parser.add_argument('-p', '--python', dest='python_path', metavar='<python>',
                        help='Path to python program (typically in a virtualenv)')
    parser.add_argument('-r', '--requirements', dest='requirements_txt',
                        metavar='<file>',
                        help='Path to requirements.txt file')
    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
                        help='Run in verbose mode')
    args = parser.parse_args()

    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.ERROR,
        format='[%(levelname)s] %(name)s %(message)s',
        stream=sys.stderr,
    )

    if not args.python_path:
        args.python_path = sys.executable

    if not args.requirements_txt:
        args.requirements_txt = os.path.abspath('requirements.txt')

    if not args.basedir:
        args.basedir = os.path.dirname(args.requirements_txt)

    linter = RequirementsLinter(args)
    exit_code = linter.run()
    sys.exit(exit_code)
