"""SQLite parser plugin for Mozilla Firefox downloads database files."""

import json

from dfdatetime import posix_time as dfdatetime_posix_time

from plaso.containers import events
from plaso.parsers import sqlite
from plaso.parsers.sqlite_plugins import interface


class FirefoxDownloadEventData(events.EventData):
    """Firefox download event data.

    Attributes:
      end_time (dfdatetime.DateTimeValues): date and time the download was
          finished.
      full_path (str): full path of the target of the download.
      mime_type (str): mime type of the download.
      name (str): name of the download.
      offset (str): identifier of the row, from which the event data was
          extracted.
      query (str): SQL query that was used to obtain the event data.
      received_bytes (int): number of bytes received.
      referrer (str): referrer URL of the download.
      start_time (dfdatetime.DateTimeValues): date and time the download was
          started.
      temporary_location (str): temporary location of the download.
      total_bytes (int): total number of bytes of the download.
      url (str): source URL of the download.
    """

    DATA_TYPE = "firefox:downloads:download"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.end_time = None
        self.full_path = None
        self.mime_type = None
        self.name = None
        self.offset = None
        self.query = None
        self.received_bytes = None
        self.referrer = None
        self.start_time = None
        self.temporary_location = None
        self.total_bytes = None
        self.url = None


class Firefox118DownloadEventData(events.EventData):
    """Firefox download event data.

    Attributes:
      deleted (int): deleted state.
      download_state (int): state of the download.
      end_time (dfdatetime.DateTimeValues): date and time the download was
          finished.
      expiration (int): expiration.
      flags (int): flags associated with this download
      full_path (str): full path of the target of the download.
      mime_type (str): mime type of the download.
      name (str): name of the download.
      offset (str): identifier of the row, from which the event data was
          extracted.
      query (str): SQL query that was used to obtain the event data.
      received_bytes (int): number of bytes received.
      referrer (str): referrer URL of the download.
      start_time (dfdatetime.DateTimeValues): date and time the download was
          started.
      temporary_location (str): temporary location of the download.
      total_bytes (int): total number of bytes of the download.
      url (str): source URL of the download.
    """

    DATA_TYPE = "firefox:downloads:download"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.deleted = None
        self.download_state = None
        self.end_time = None
        self.expiration = None
        self.flags = None
        self.full_path = None
        self.name = None
        self.query = None
        self.received_bytes = None
        self.start_time = None
        self.total_bytes = None
        self.url = None


class FirefoxDownloadsPlugin(interface.SQLitePlugin):
    """SQLite parser plugin for Mozilla Firefox downloads database files.

    The Mozilla Firefox downloads database file is typically stored in:
    downloads.sqlite
    """

    NAME = "firefox_downloads"
    DATA_FORMAT = "Mozilla Firefox downloads SQLite database (downloads.sqlite) file"

    REQUIRED_STRUCTURE = {
        "moz_downloads": frozenset(
            [
                "id",
                "name",
                "source",
                "target",
                "tempPath",
                "startTime",
                "endTime",
                "state",
                "referrer",
                "currBytes",
                "maxBytes",
                "mimeType",
            ]
        )
    }

    QUERIES = [
        (
            (
                "SELECT moz_downloads.id, moz_downloads.name, moz_downloads.source, "
                "moz_downloads.target, moz_downloads.tempPath, "
                "moz_downloads.startTime, moz_downloads.endTime, moz_downloads.state, "
                "moz_downloads.referrer, moz_downloads.currBytes, "
                "moz_downloads.maxBytes, moz_downloads.mimeType "
                "FROM moz_downloads"
            ),
            "ParseDownloadsRow",
        )
    ]

    SCHEMAS = [
        {
            "moz_downloads": "CREATE TABLE moz_downloads (id INTEGER PRIMARY KEY, "
            "name TEXT, source TEXT, target TEXT, tempPath TEXT, startTime INTEGER, "
            "endTime INTEGER, state INTEGER, referrer TEXT, entityID TEXT, currBytes "
            "INTEGER NOT NULL DEFAULT 0, maxBytes INTEGER NOT NULL DEFAULT -1, "
            "mimeType TEXT, preferredApplication TEXT, preferredAction INTEGER "
            "NOT NULL DEFAULT 0, autoResume INTEGER NOT NULL DEFAULT 0)"
        }
    ]

    def ParseDownloadsRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a downloads row.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers
              and other components, such as storage and dfVFS.
          query (str): query that created the row.
          row (sqlite3.Row): row.
        """
        query_hash = hash(query)

        event_data = FirefoxDownloadEventData()
        event_data.end_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "endTime"
        )
        event_data.full_path = self._GetRowValue(query_hash, row, "target")
        event_data.mime_type = self._GetRowValue(query_hash, row, "mimeType")
        event_data.name = self._GetRowValue(query_hash, row, "name")
        event_data.offset = self._GetRowValue(query_hash, row, "id")
        event_data.query = query
        event_data.received_bytes = self._GetRowValue(query_hash, row, "currBytes")
        event_data.referrer = self._GetRowValue(query_hash, row, "referrer")
        event_data.start_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "startTime"
        )
        event_data.temporary_location = self._GetRowValue(query_hash, row, "tempPath")
        event_data.total_bytes = self._GetRowValue(query_hash, row, "maxBytes")
        event_data.url = self._GetRowValue(query_hash, row, "source")

        parser_mediator.ProduceEventData(event_data)


class Firefox118DownloadsPlugin(interface.SQLitePlugin):
    """SQLite parser plugin for version 118 Firefox downloads database files.

    The version 118 Firefox downloads database file is typically stored in:
    places.sql
    """

    NAME = "firefox_118_downloads"
    DATA_FORMAT = (
        "Mozilla Firefox 118 downloads SQLite database (downloads.sqlite) file"
    )

    REQUIRED_STRUCTURE = {
        "moz_annos": frozenset(
            [
                "id",
                "place_id",
                "anno_attribute_id",
                "content",
                "flags",
                "expiration",
                "type",
                "dateAdded",
                "lastModified",
            ]
        ),
        "moz_places": frozenset(["id", "title", "url", "last_visit_date"]),
    }

    QUERIES = [
        (
            (
                "SELECT annos1.content, annos2.flags, annos2.expiration, annos2.type, "
                "annos2.dateAdded, annos2.lastModified, annos2.content as dest_fpath, "
                "places.url, places.title, places.last_visit_date "
                "FROM moz_annos annos1, moz_annos annos2, moz_places places "
                "WHERE annos1.anno_attribute_id == (annos2.anno_attribute_id + 1) "
                "AND annos1.place_id == annos2.place_id "
                "AND annos1.place_id == places.id"
            ),
            "ParseDownloadsRow",
        )
    ]

    SCHEMAS = [
        {
            "moz_annos": "CREATE TABLE moz_annos (id INTEGER PRIMARY KEY, "
            "place_id INTEGER NOT NULL, anno_attribute_id INTEGER, "
            "content LONGVARCHAR, flags INTEGER DEFAULT 0, "
            "expiration INTEGER DEFAULT 0, type INTEGER DEFAULT 0, "
            "dateAdded INTEGER DEFAULT 0, lastModified INTEGER DEFAULT 0)"
        },
        {
            "moz_places": "CREATE TABLE moz_places (id INTEGER PRIMARY KEY, "
            "url LONGVARCHAR, title LONGVARCHAR, rev_host LONGVARCHAR, "
            "visit_count INTEGER DEFAULT 0, hidden INTEGER DEFAULT 0 NOT NULL, "
            "typed INTEGER DEFAULT 0 NOT NULL, "
            "frecency INTEGER DEFAULT -1 NOT NULL, last_visit_date INTEGER, "
            "guid TEXT, foreign_count INTEGER DEFAULT 0 NOT NULL, "
            "url_hash INTEGER DEFAULT 0 NOT NULL , description TEXT, "
            "preview_image_url TEXT, site_name TEXT, "
            "origin_id INTEGER REFERENCES moz_origins(id), "
            "recalc_frecency INTEGER NOT NULL DEFAULT 0, alt_frecency INTEGER, "
            "recalc_alt_frecency INTEGER NOT NULL DEFAULT 0)"
        },
    ]

    def ParseDownloadsRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a downloads row.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers
              and other components, such as storage and dfVFS.
          query (str): query that created the row.
          row (sqlite3.Row): row.
        """
        query_hash = hash(query)

        content = self._GetRowValue(query_hash, row, "content")

        # Content consists of a JSON object similar to:
        # {
        #   'state': 1,
        #   'deleted': False,
        #   'endTime': 1689722333589,
        #   'fileSize': 66383750
        # }
        content_data = json.loads(content)

        event_data = Firefox118DownloadEventData()

        event_data.deleted = content_data.get("deleted")
        event_data.download_state = content_data.get("state")

        end_time = content_data.get("endTime")
        if end_time is not None:
            event_data.end_time = dfdatetime_posix_time.PosixTimeInMilliseconds(
                timestamp=end_time
            )

        event_data.expiration = self._GetRowValue(query_hash, row, "expiration")
        event_data.flags = self._GetRowValue(query_hash, row, "flags")
        event_data.full_path = self._GetRowValue(query_hash, row, "dest_fpath")
        event_data.name = self._GetRowValue(query_hash, row, "title")
        event_data.query = query
        event_data.received_bytes = content_data.get("fileSize", 0)
        event_data.start_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "dateAdded"
        )
        event_data.total_bytes = content_data.get("fileSize", 0)
        # TODO: re-add with more specific attribute name
        # self._GetRowValue(query_hash, row, "type")
        event_data.url = self._GetRowValue(query_hash, row, "url")

        parser_mediator.ProduceEventData(event_data)


sqlite.SQLiteParser.RegisterPlugins([FirefoxDownloadsPlugin, Firefox118DownloadsPlugin])
