# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
#   https://license.mvt.re/1.1/

import logging
from pathlib import Path

import pytest
from click.testing import CliRunner

from mvt.android.cli import check_androidqf
from mvt.android.modules.androidqf.aqf_packages import AQFPackages
from mvt.android.modules.androidqf import aqf_packages as aqf_packages_module
from mvt.common.module import run_module

from ..utils import get_android_androidqf, list_files


@pytest.fixture()
def data_path():
    return get_android_androidqf()


@pytest.fixture()
def parent_data_path(data_path):
    return Path(data_path).absolute().parent.as_posix()


@pytest.fixture()
def file_list(data_path):
    return list_files(data_path)


@pytest.fixture()
def module(parent_data_path, file_list):
    m = AQFPackages(target_path=parent_data_path, log=logging)
    m.from_dir(parent_data_path, file_list)
    return m


class TestAndroidqfPackages:
    def test_packages_list(self, module):
        run_module(module)

        # There should just be 7 packages listed, no detections
        assert len(module.results) == 7
        assert len(module.timeline) == 0

    def test_non_appstore_warnings(self, caplog, module):
        run_module(module)

        assert len(module.alertstore.alerts) == 5

        # Not a super test to be searching logs for this but heuristic detections not yet formalised
        adb_message = "Found a non-system package installed via adb or another method:"
        whatsapp_detected = [
            alert
            for alert in module.alertstore.alerts
            if alert.event["name"] == "com.whatsapp"
        ]
        assert len(whatsapp_detected) == 1
        assert adb_message in whatsapp_detected[0].message

        browser_message = 'Found a package installed via a browser (installer="com.google.android.packageinstaller"): '
        revanced_detected = [
            alert
            for alert in module.alertstore.alerts
            if alert.event["name"] == "app.revanced.manager.flutter"
        ]
        assert len(revanced_detected) == 1
        assert browser_message in revanced_detected[0].message

        third_party_message = 'Found a package installed via a third party store (installer="org.fdroid.fdroid")'
        appollo_detected = [
            alert
            for alert in module.alertstore.alerts
            if alert.event["name"] == "org.nuclearfog.apollo"
        ]
        assert len(appollo_detected) == 1
        assert third_party_message in appollo_detected[0].message

    def test_packages_ioc_package_names(self, module, indicators_factory):
        module.indicators = indicators_factory(app_ids=["com.malware.blah"])

        run_module(module)

        possible_detected_app = [
            alert
            for alert in module.alertstore.alerts
            if alert.event["name"] == "com.malware.blah"
        ]
        assert len(possible_detected_app) == 1
        assert possible_detected_app[0].event["name"] == "com.malware.blah"
        assert possible_detected_app[0].matched_indicator.value == "com.malware.blah"

    def test_packages_ioc_sha256(self, module, indicators_factory):
        module.indicators = indicators_factory(
            files_sha256=[
                "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa"
            ]
        )

        run_module(module)

        possible_detected_app = [
            alert
            for alert in module.alertstore.alerts
            if alert.event["name"] == "com.malware.muahaha"
        ]
        assert len(possible_detected_app) == 1
        assert possible_detected_app[0].event["name"] == "com.malware.muahaha"
        assert (
            possible_detected_app[0].matched_indicator.value
            == "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa"
        )

    def test_packages_certificate_hash_ioc(self, module, indicators_factory):
        module.indicators = indicators_factory(
            app_cert_hashes=[
                "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730"
            ]
        )

        run_module(module)

        possible_detected_app = [
            alert
            for alert in module.alertstore.alerts
            if alert.event["name"] == "com.malware.muahaha"
        ]
        assert len(possible_detected_app) == 1
        assert possible_detected_app[0].event["name"] == "com.malware.muahaha"
        assert (
            possible_detected_app[0].matched_indicator.value
            == "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730"
        )

    def test_virustotal_delays_after_missing_result(self, monkeypatch):
        lookups = []
        sleeps = []

        def fake_virustotal_lookup(file_hash):
            lookups.append(file_hash)
            if file_hash == "missing_hash":
                return None
            return {
                "attributes": {
                    "last_analysis_stats": {"malicious": 1},
                    "last_analysis_results": {"engine": {}},
                }
            }

        monkeypatch.setattr(
            aqf_packages_module, "virustotal_lookup", fake_virustotal_lookup
        )
        monkeypatch.setattr(aqf_packages_module.time, "sleep", sleeps.append)

        module = AQFPackages(
            module_options={"virustotal": True, "virustotal_delay": 16},
            results=[
                {
                    "name": "org.example",
                    "installer": "com.android.vending",
                    "disabled": False,
                    "system": False,
                    "files": [
                        {"path": "/data/app/missing.apk", "sha256": "missing_hash"},
                        {"path": "/data/app/found.apk", "sha256": "found_hash"},
                    ],
                }
            ],
        )

        module.check_indicators()

        assert lookups == ["missing_hash", "found_hash"]
        assert sleeps == [16]
        assert module.results[0]["files"][1]["virustotal"] == "1/1"
        assert len(module.alertstore.alerts) == 1


def test_check_androidqf_rejects_negative_virustotal_delay(data_path):
    runner = CliRunner()

    result = runner.invoke(check_androidqf, ["--delay", "-1", data_path])

    assert result.exit_code == 2
    assert "Invalid value for '--delay'" in result.output
