import difflib
import glob
import io
import json
import shutil
from pathlib import Path
import os.path
from typing import List, Optional, Callable, Any, Tuple
from pprint import pprint

from loguru import logger
import pytest
from pytest_steps import test_steps
from copy import deepcopy

from template_scan2report import TemplateScan2Report
from tests.test_findings_parsing import find_all_scanner_test_files
from scan2report import scan2report as s2r
from scan2report.scan2report import Scan2ReportData, Scan2ReportConfig
from helpers.file_utils import json_safe_load, json_dump, save_file
from tests.helper_anotate_tests import skip_confidential
from tests.conftest import TMP_PATH
import config

LOCALE = 'cs'


def input_files():
    files = find_all_scanner_test_files()
    files = skip_confidential(files)
    return files


def templates_path(locale: str = LOCALE, original: bool = False) -> str:
    return TemplateScan2Report.native_folder_name_for_locale(locale, original)


def list_json_files(folder_path: str) -> List[str]:
    return glob.glob(f'{folder_path}/*.json')


def move_og_templates(locale: str = LOCALE):
    template_path = templates_path(locale)
    og_templates = list_json_files(templates_path(locale, original=True))

    for single_template in og_templates:
        shutil.move(single_template, template_path)


def delete_templates():
    all_templates = set()
    all_templates.update(list_json_files(templates_path(LOCALE, False)))
    all_templates.update(list_json_files(templates_path(LOCALE, True)))
    for single_template in all_templates:
        os.unlink(single_template)


def output_path_by_filename(filename: str, file_extension: str = 'json') -> str:
    output_path = os.path.join(TMP_PATH, f'{filename}.{file_extension}')
    return output_path


def current_result_by_filename(filename: str) -> dict:
    output_path = output_path_by_filename(filename)
    with open(output_path, encoding="utf8") as f:
        data = json.load(f)
        return data


def correct_result_from_previous_versions(filename: str) -> dict:
    filepath = f'tests/test_files/scan2report_regression/{filename}.json'
    with open(filepath, encoding="utf8") as f:
        data = json.load(f)
        return data


def print_text_diff(a: str, b: str):
    # https://stackoverflow.com/a/17904977/5769940
    print('{} => {}'.format(a, b))
    for i, s in enumerate(difflib.ndiff(a, b)):
        if s[0] == ' ':
            continue
        elif s[0] == '-':
            print(u'Delete "{}" from position {}'.format(s[-1], i))
        elif s[0] == '+':
            print(u'Add "{}" to position {}'.format(s[-1], i))


class TestScan2Report:
    def run_s2r_twice(self, filepath, file_extension: str, s2r_root_dir: str = None) -> Tuple[str, Scan2ReportData]:
        root_dir = s2r_root_dir if s2r_root_dir else config.SCAN2REPORT_ALTERNATIVE_FOLDER
        filename = os.path.basename(filepath)
        output_path = output_path_by_filename(filename, file_extension)
        args = ['--output', output_path, '--lang', LOCALE, filepath]
        s2r_config = Scan2ReportConfig(rootDir=root_dir)

        delete_templates()
        s2r.main(args, s2r_c=s2r_config)
        move_og_templates()
        data_answer = s2r.main(args, s2r_c=s2r_config)

        assert len(data_answer.missingPlugins) == 0
        return filename, data_answer

    @pytest.mark.parametrize("filepath", input_files())
    def test_parsing_consistency(self, filepath):
        filename, _ = self.run_s2r_twice(filepath, 'json')

        current_result = current_result_by_filename(filename)
        correct_result = correct_result_from_previous_versions(filename)

        assert len(current_result) == len(correct_result), "The number of findings doesn't match"
        for i in range(len(current_result)):
            try:
                current_finding = current_result[i]
                correct_finding = correct_result[i]

                assert current_finding['fid'] == correct_finding['fid']
                for single_key in correct_finding:
                    if current_finding['description'] != correct_finding['description']:
                        save_file(current_finding['description'], output_path_by_filename(filename + '_current_description'))
                        save_file(correct_finding['description'], output_path_by_filename(filename + '_correct_description'))
                        if current_finding['hosts'] != correct_finding['hosts']:
                            logger.warning("Description is different, but it may be because .hosts might be in different order. Skipping for now.")
                            continue

                    if not isinstance(current_finding[single_key], list):
                        assert current_finding[single_key] == correct_finding[single_key], f'Failed on key "{single_key}"'
                    else:
                        # Todo: remove this workaround, it masks .hosts being in wrong order. It doesn't mask .description which is generated based on .hosts
                        assert sorted(current_finding[single_key]) == sorted(correct_finding[single_key]), f'Failed on key "{single_key}"'

                # assert current_finding == correct_finding
            except AssertionError:
                print(f'Failed for {filename}, FID {current_finding["fid"]}')
                json_dump(current_result[i], output_path_by_filename(filename + '_current'))
                json_dump(correct_result[i], output_path_by_filename(filename + '_correct'))
                raise

    def test_help(self):
        Scan2ReportConfig().usage()

    def check_if_file_is_produced(self, filepath, file_extension):
        filename, data_answer = self.run_s2r_twice(filepath, file_extension)
        output_filepath = output_path_by_filename(filename, file_extension)

        if len(data_answer.findings) == 0 and file_extension == 'csv':
            pass
        else:
            assert Path(output_filepath).is_file()

        Path(output_filepath).unlink(missing_ok=True)

    @pytest.mark.parametrize("filepath", input_files())
    def test_csv_production(self, filepath):
        # todo: csv is not outputed if there are no findings
        self.check_if_file_is_produced(filepath, 'csv')

    @pytest.mark.parametrize("filepath", input_files()[6:])  # todo: generation of [0] takes too long
    def test_docx_production(self, filepath):
        self.check_if_file_is_produced(filepath, 'docx')
