# 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

from mvt.common.indicators import Indicator, IndicatorMatch
from mvt.common.module import run_module

from ..utils import get_android_androidqf, list_files


class TestAndroidqfMountsArtifact:
    def test_parse_mounts_token_checks(self):
        """
        Test the artifact-level `parse` method using tolerant token checks.

        Different parser variants may place mount tokens into different dict
        keys (for example `mount_options`, `pass_num`, `dump_freq`, etc.). To
        avoid brittle assertions we concatenate each parsed entry's values and
        look for expected tokens (device names, mount points, options) somewhere
        in the combined representation.
        """
        from mvt.android.artifacts.mounts import Mounts as MountsArtifact

        m = MountsArtifact()

        mount_lines = [
            "/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)",
            "/dev/block/by-name/system on /system type ext4 (rw,seclabel,noatime)",
            "/dev/block/by-name/data on /data type f2fs (rw,nosuid,nodev,noatime)",
        ]
        mount_content = "\n".join(mount_lines)

        # Parse the mount lines (artifact-level)
        m.parse(mount_content)

        # Basic sanity: parser should return one entry per input line
        assert len(m.results) == 3, f"Expected 3 parsed mounts, got: {m.results}"

        # Concatenate each entry's values into a single string so token checks
        # are tolerant to which dict keys were used by the parser.
        def concat_values(entry):
            parts = []
            for v in entry.values():
                try:
                    parts.append(str(v))
                except Exception:
                    # Skip values that can't be stringified
                    continue
            return " ".join(parts)

        concatenated = [concat_values(e) for e in m.results]

        # Token expectations (tolerant):
        # - Root line should include 'dm-12' and 'noatime' (and typically 'ro')
        assert any("dm-12" in s and "noatime" in s for s in concatenated), (
            f"No root-like tokens (dm-12 + noatime) found in parsed results: {concatenated}"
        )

        # - System line should include '/system' or 'by-name/system' and 'rw'
        assert any(
            (("by-name/system" in s or "/system" in s) and "rw" in s)
            for s in concatenated
        ), (
            f"No system-like tokens (system + rw) found in parsed results: {concatenated}"
        )

        # - Data line should include '/data' or 'by-name/data' and 'rw'
        assert any(
            (("by-name/data" in s or "/data" in s) and "rw" in s) for s in concatenated
        ), f"No data-like tokens (data + rw) found in parsed results: {concatenated}"

    def test_mount_ioc_alert_uses_indicator(self):
        from mvt.android.artifacts.mounts import Mounts as MountsArtifact

        indicator = Indicator(
            value="/system",
            type="file_path",
            name="TestMalware",
            stix2_file_name="indicators.stix2",
        )
        m = MountsArtifact()
        m.indicators = type(
            "MountIndicators",
            (),
            {
                "check_file_path": lambda self, path: IndicatorMatch(
                    ioc=indicator, message="matched file path"
                )
                if path == "/system"
                else None
            },
        )()

        m.parse("/dev/block/by-name/system on /system type ext4 (rw,seclabel)")
        m.check_indicators()

        indicator_alerts = [
            alert for alert in m.alertstore.alerts if alert.matched_indicator
        ]
        assert len(indicator_alerts) == 1
        assert indicator_alerts[0].matched_indicator == indicator
        assert indicator_alerts[0].message == "matched file path"


class TestAndroidqfMountsModule:
    def test_androidqf_module_no_mounts_file(self):
        """
        When no `mounts.json` is present in the androidqf dataset, the module
        should not produce results nor detections.
        """
        from mvt.android.modules.androidqf.mounts import Mounts

        data_path = get_android_androidqf()
        m = Mounts(target_path=data_path, log=logging)
        files = list_files(data_path)
        parent_path = Path(data_path).absolute().parent.as_posix()
        m.from_dir(parent_path, files)

        run_module(m)

        # The provided androidqf test dataset does not include mounts.json, so
        # results should remain empty.
        assert len(m.results) == 0, (
            f"Expected no results when mounts.json is absent, got: {m.results}"
        )
        assert len(m.alertstore.alerts) == 0, (
            f"Expected no detections, got: {m.alertstore.alerts}"
        )
