# -*- coding: utf_8 -*-
import logging
from pathlib import Path

from django.conf import settings

from mobsf.MobSF.utils import (
    disable_print,
    enable_print,
)

logger = logging.getLogger(__name__)


def quark_analysis(app_dir, apk_file):
    """QUARK Analysis of APK files."""
    if not getattr(settings, 'QUARK_ENABLED', True):
        return []
    try:
        import quark
    except ImportError:
        logger.error('Failed to import Quark')
        return []
    if not Path(apk_file).exists():
        logger.error('APK not found')
        return []

    quark_ver = quark.__version__

    from quark import config
    from quark.freshquark import download
    from quark.report import Report

    logger.info('Running Quark %s', quark_ver)
    json_report = {}
    try:
        # freshquark: update quark rules
        disable_print()
        download()
        enable_print()

        # default rules path
        rules_dir = Path(f'{config.HOME_DIR}quark-rules')
        report = Report()

        # Analyze apk
        report.analysis(apk_file, rules_dir)

        # Generate Report
        json_report = report.get_report('json')
        # Clear all functools.lru_cache from quark
        report.quark.apkinfo.find_method.cache_clear()
        report.quark.apkinfo.upperfunc.cache_clear()
        report.quark.apkinfo.get_wrapper_smali.cache_clear()
    except Exception:
        logger.exception('Quark APK Analysis')
    return _convert_report(json_report, app_dir)


def _convert_report(origin_report, app_dir):
    new_report = []
    if not origin_report or not origin_report.get('crimes'):
        logger.warning('Skipping Quark Analysis')
        return new_report
    for crime in origin_report.get('crimes'):
        if not crime['confidence'] == '100%':
            continue

        new_crime = {}
        new_crime['crime'] = crime['crime']
        new_crime['score'] = crime['score']
        new_crime['weight'] = crime['weight']
        new_crime['confidence'] = crime['confidence']
        new_crime['permissions'] = crime['permissions']
        new_crime['register'] = []

        for item in crime['register']:
            source_code = {}
            cls_and_md = next(iter(item))
            detail = item[cls_and_md]

            file_path = cls_and_md.split(' ')[0].replace(';', '.smali')[1:]
            method = ''.join(cls_and_md.split(' ')[1:])
            source_code['file'] = file_path
            source_code['method'] = method
            source_code['first_api'] = detail['first']
            source_code['second_api'] = detail['second']

            source_code_dir = Path(app_dir) / 'smali_source' / file_path
            source_code['line_numbers'] = _get_line_numbers(
                source_code_dir,
                method,
                detail['first'],
                detail['second'],
            )

            new_crime['register'].append(source_code)

        new_report.append(new_crime)

    return new_report


def _get_line_numbers(source_code_path, method, first_api, second_api):
    line_numbers = {
        'method_start': -1,
        'method_end': -1,
        'first_api': -1,
        'second_api': -1,
    }
    method_found = False

    try:
        with open(source_code_path, 'r') as file:
            for num, line in enumerate(file, 1):
                if (not method_found
                    and line.startswith('.method')
                        and method in line):
                    line_numbers['method_start'] = num
                    method_found = True
                elif method_found:
                    first_api_opcode = first_api[0]
                    first_api_method = first_api[-1].replace(' ', '')
                    if first_api_opcode in line and first_api_method in line:
                        line_numbers['first_api'] = num

                    second_api_opcode = second_api[0]
                    second_api_method = second_api[-1].replace(' ', '')
                    if second_api_opcode in line and second_api_method in line:
                        line_numbers['second_api'] = num

                    if line.startswith('.end method'):
                        line_numbers['method_end'] = num
                        return line_numbers
        return line_numbers
    except EnvironmentError:
        return line_numbers

    except Exception:
        logger.exception('Finding line numbers of method and apis')
        return line_numbers
