# 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 datetime
import json
import logging

try:
    import zoneinfo
except ImportError:
    from backports import zoneinfo  # type: ignore
from typing import Optional

from mvt.android.modules.androidqf.base import AndroidQFModule
from mvt.common.module_types import (
    ModuleAtomicResult,
    ModuleResults,
    ModuleSerializedResult,
)
from mvt.common.utils import convert_datetime_to_iso

SUSPICIOUS_PATHS = [
    "/data/local/tmp/",
]


class AQFFiles(AndroidQFModule):
    """
    This module analyzes the files.json dump generated by AndroidQF.

    The format needs to be kept in sync with the AndroidQF module code.
    https://github.com/mvt-project/androidqf/blob/main/android-collector/cmd/find.go#L28
    """

    def __init__(
        self,
        file_path: Optional[str] = None,
        target_path: Optional[str] = None,
        results_path: Optional[str] = None,
        module_options: Optional[dict] = None,
        log: logging.Logger = logging.getLogger(__name__),
        results: Optional[ModuleResults] = None,
    ) -> None:
        super().__init__(
            file_path=file_path,
            target_path=target_path,
            results_path=results_path,
            module_options=module_options,
            log=log,
            results=results,
        )

    def serialize(self, record: ModuleAtomicResult) -> ModuleSerializedResult:
        records = []

        for ts in set(
            [record["access_time"], record["changed_time"], record["modified_time"]]
        ):
            macb = ""
            macb += "M" if ts == record["modified_time"] else "-"
            macb += "A" if ts == record["access_time"] else "-"
            macb += "C" if ts == record["changed_time"] else "-"
            macb += "-"

            msg = record["path"]
            if record["context"]:
                msg += f" ({record['context']})"

            records.append(
                {
                    "timestamp": ts,
                    "module": self.__class__.__name__,
                    "event": macb,
                    "data": msg,
                }
            )

        return records

    def file_is_executable(self, mode_string):
        return "x" in mode_string

    def check_indicators(self) -> None:
        if not self.indicators:
            return

        for result in self.results:
            ioc_match = self.indicators.check_file_path(result["path"])
            if ioc_match:
                self.alertstore.critical(
                    ioc_match.message, "", result, matched_indicator=ioc_match.ioc
                )
                continue

            # NOTE: Update with final path used for Android collector.
            if result["path"] == "/data/local/tmp/collector":
                continue

            for suspicious_path in SUSPICIOUS_PATHS:
                if result["path"].startswith(suspicious_path):
                    file_type = ""
                    if self.file_is_executable(result["mode"]):
                        file_type = "executable "

                    msg = f'Found {file_type}file at suspicious path "{result["path"]}"'
                    self.alertstore.high(msg, "", result)

            for hash_key in ("sha256", "sha1", "md5"):
                file_hash = result.get(hash_key, "")
                if not file_hash:
                    continue
                ioc_match = self.indicators.check_file_hash(file_hash)
                if ioc_match:
                    self.alertstore.critical(
                        ioc_match.message, "", result, matched_indicator=ioc_match.ioc
                    )
                    break

    def run(self) -> None:
        if timezone := self._get_device_timezone():
            device_timezone = zoneinfo.ZoneInfo(timezone)
        else:
            self.log.warning("Unable to determine device timezone, using UTC")
            device_timezone = zoneinfo.ZoneInfo("UTC")

        for file in self._get_files_by_pattern("*/files.json"):
            rawdata = self._get_file_content(file).decode("utf-8", errors="ignore")
            try:
                data = json.loads(rawdata)
            except json.decoder.JSONDecodeError:
                data = []
                for line in rawdata.splitlines():
                    if line.strip() == "":
                        continue
                    data.append(json.loads(line))

            for file_data in data:
                for ts in ["access_time", "changed_time", "modified_time"]:
                    if ts in file_data:
                        utc_timestamp = datetime.datetime.fromtimestamp(
                            file_data[ts], tz=datetime.timezone.utc
                        )
                        # Convert the UTC timestamp to local time on Android device's local timezone
                        local_timestamp = utc_timestamp.astimezone(device_timezone)

                        # Preserve the device-local wall-clock time while using
                        # the project-wide ISO conversion helper.
                        local_timestamp = local_timestamp.replace(
                            tzinfo=datetime.timezone.utc
                        )
                        file_data[ts] = convert_datetime_to_iso(local_timestamp)

                self.results.append(file_data)

            break  # Only process the first matching file

        self.log.info("Found a total of %d files", len(self.results))
