# -*- coding: utf-8 -*-
import os
import sys
import importlib

from io import StringIO
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase, override_settings

from django_extensions.management.commands.runscript import Command, BadCustomDirectoryException, DirPolicyChoices


class RunScriptTests(TestCase):
    def setUp(self):
        sys.stdout = StringIO()
        sys.stderr = StringIO()

    def get_command(self):
        cmd = Command()
        cmd.running_tests = True
        return cmd

    def test_runs(self):
        # lame test...does it run?
        call_command('runscript', 'sample_script', verbosity=2)
        self.assertIn("Found script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue())
        self.assertIn("Running script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue())

    def test_runs_appconfig(self):
        with self.modify_settings(INSTALLED_APPS={
            'append': 'tests.testapp.apps.TestAppConfig',
            'remove': 'tests.testapp',
        }):
            call_command('runscript', 'sample_script', verbosity=2)
            self.assertIn("Found script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue())
            self.assertIn("Running script 'tests.testapp.scripts.sample_script'", sys.stdout.getvalue())


class NonExistentScriptsTests(RunScriptTests):
    def test_prints_error_on_nonexistent_script(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'non_existent_script', verbosity=2)
        self.assertIn("No (valid) module for script 'non_existent_script' found", sys.stdout.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)

    def test_prints_nothing_for_nonexistent_script_when_silent(self):
        cmd = self.get_command()
        call_command(cmd, 'non_existent_script', silent=True)
        self.assertEqual("", sys.stdout.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)

    def test_doesnt_print_exception_for_nonexistent_script_when_no_traceback(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'non_existent_script', no_traceback=True)
        self.assertEqual("", sys.stderr.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)


class InvalidImportScriptsTests(RunScriptTests):
    def test_prints_additional_info_on_nonexistent_script_by_default(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'non_existent_script')
        self.assertIn("No (valid) module for script 'non_existent_script' found", sys.stdout.getvalue())
        self.assertIn("Try running with a higher verbosity level like: -v2 or -v3", sys.stdout.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)

    def test_prints_import_error_on_script_with_invalid_imports_by_default(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'invalid_import_script')
        self.assertIn("Cannot import module 'tests.testapp.scripts.invalid_import_script'", sys.stdout.getvalue())
        self.assertRegex(sys.stdout.getvalue(), 'No module named (\')?(invalidpackage)\1?')
        self.assertEqual(cmd.last_exit_code, 1)

    def test_prints_import_error_on_script_with_invalid_imports_reliably(self):
        cmd = self.get_command()
        if hasattr(importlib, 'util') and hasattr(importlib.util, 'find_spec'):
            with self.settings(BASE_DIR=os.path.dirname(os.path.abspath(__file__))):
                with self.assertRaises(CommandError):
                    call_command(cmd, 'invalid_import_script')
                self.assertIn("Cannot import module 'tests.testapp.scripts.invalid_import_script'", sys.stdout.getvalue())
                self.assertRegex(sys.stdout.getvalue(), 'No module named (\')?(invalidpackage)\1?')
                self.assertEqual(cmd.last_exit_code, 1)


class InvalidScriptsTests(RunScriptTests):
    def test_raises_error_message_on_invalid_script_by_default(self):
        cmd = self.get_command()
        with self.assertRaises(Exception):
            call_command(cmd, 'error_script')
        self.assertIn("Exception while running run() in", sys.stdout.getvalue())

    def test_prints_nothing_for_invalid_script_when_silent(self):
        cmd = self.get_command()
        call_command(cmd, 'error_script', silent=True)
        self.assertEqual(cmd.last_exit_code, 1)
        self.assertEqual("", sys.stdout.getvalue())

    def test_doesnt_print_exception_for_nonexistent_script_when_no_traceback(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'error_script', no_traceback=True)
        self.assertEqual("", sys.stderr.getvalue())
        self.assertIn("Exception while running run() in", sys.stdout.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)


class RunFunctionTests(RunScriptTests):
    def test_prints_error_message_for_script_without_run(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'script_no_run_function')
        self.assertIn("No (valid) module for script 'script_no_run_function' found", sys.stdout.getvalue())
        self.assertIn("Try running with a higher verbosity level like: -v2 or -v3", sys.stdout.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)

    def test_prints_additional_info_for_script__run_extra_verbosity(self):
        cmd = self.get_command()
        with self.assertRaises(CommandError):
            call_command(cmd, 'script_no_run_function', verbosity=2)
        self.assertIn("No (valid) module for script 'script_no_run_function' found", sys.stdout.getvalue())
        self.assertIn("Found script", sys.stdout.getvalue())
        self.assertEqual(cmd.last_exit_code, 1)

    def test_prints_nothing_for_script_without_run(self):
        cmd = self.get_command()
        call_command(cmd, 'script_no_run_function', silent=True)
        self.assertEqual("", sys.stdout.getvalue())


project_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))


class ChangingDirectoryTests(RunScriptTests):
    def setUp(self):
        super().setUp()
        self.curwd = os.getcwd()
        os.chdir(project_path)

    def tearDown(self):
        super().setUp()
        os.chdir(self.curwd)

    def _execute_script_with_chdir(self, dir_policy, start_path, expected_path, chdir=None):
        os.chdir(os.path.join(project_path, *start_path))
        expected_path = os.path.join(project_path, *expected_path)
        call_command('runscript', 'directory_checker_script', dir_policy=dir_policy, chdir=chdir)
        output = sys.stdout.getvalue().split('Script called from: ')[1]
        self.assertEqual(output, expected_path + '\n')

    def test_none_policy_command_run(self):
        self._execute_script_with_chdir(DirPolicyChoices.NONE, [], [])

    def test_none_policy_command_run_with_chdir(self):
        self._execute_script_with_chdir(DirPolicyChoices.NONE, ['tests'], ['tests'])

    def test_none_policy_freezing_start_directory(self):
        self._execute_script_with_chdir(DirPolicyChoices.NONE, ['tests'], ['tests'])
        self._execute_script_with_chdir(DirPolicyChoices.NONE, ['tests'], ['tests'])

    def test_root_policy_command_run(self):
        self._execute_script_with_chdir(DirPolicyChoices.ROOT, ['tests'], [])

    def test_each_policy_command_run(self):
        os.chdir(os.path.join(project_path, 'tests'))
        call_command('runscript', 'directory_checker_script', 'other_directory_checker_script', dir_policy=DirPolicyChoices.EACH)
        output = sys.stdout.getvalue()
        first_output = output.split('Script called from: ')[1].split('Cannot import module ')[0]
        self.assertEqual(first_output, os.path.join(project_path, 'tests', 'testapp', 'scripts') + '\n')
        second_output = output.split('Script called from: ')[2].split('Cannot import module ')[0]
        self.assertEqual(second_output,
                         os.path.join(project_path, 'tests', 'testapp_with_no_models_file', 'scripts') + '\n')

    def test_chdir_specified(self):
        execution_path = os.path.join(project_path, 'django_extensions', 'management')
        self._execute_script_with_chdir(DirPolicyChoices.ROOT, ['tests'], ['django_extensions', 'management'],
                                        chdir=execution_path)

    @override_settings(RUNSCRIPT_CHDIR=os.path.join(project_path, 'tests'))
    def test_policy_from_cli_and_chdir_from_settings(self):
        self._execute_script_with_chdir(DirPolicyChoices.ROOT, ['tests'], [])

    @override_settings(
        RUNSCRIPT_CHDIR=os.path.join(project_path, 'django_extensions', 'management'),
        RUNSCRIPT_CHDIR_POLICY=DirPolicyChoices.ROOT,
    )
    def test_chdir_from_settings_and_policy_from_settings(self):
        self._execute_script_with_chdir(None, ['tests'], ['django_extensions', 'management'])

    @override_settings(RUNSCRIPT_CHDIR_POLICY=DirPolicyChoices.EACH)
    def test_policy_from_settings(self):
        self._execute_script_with_chdir(None, ['tests'], ['tests', 'testapp', 'scripts'])

    @override_settings(RUNSCRIPT_CHDIR=os.path.join(project_path, 'tests'))
    def test_chdir_django_settings(self):
        self._execute_script_with_chdir(None, [], ['tests'])

    @override_settings(RUNSCRIPT_CHDIR='bad path')
    def test_custom_policy_django_settings_bad_path(self):
        with self.assertRaisesRegex(
            BadCustomDirectoryException,
            'bad path is not a directory! If --dir-policy is custom than you must set '
            'correct directory in --dir option or in settings.RUNSCRIPT_CHDIR'
        ):
            self._execute_script_with_chdir(None, [], ['tests'])

    def test_skip_printing_modules_which_does_not_exist(self):
        call_command('runscript', 'directory_checker_script')
        self.assertNotIn('No module named', sys.stdout.getvalue())
        self.assertNotIn('No module named', sys.stderr.getvalue())
