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

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


class FirefoxPlacesBookmarkEventData(events.EventData):
    """Firefox bookmark event data.

    Attributes:
      added_time (dfdatetime.DateTimeValues): date and time the bookmark was
          added.
      bookmark_type (int): bookmark type.
      hostname (str): name of the visited host or domain.
      modification_time (dfdatetime.DateTimeValues): date and time the bookmark
          was last modified.
      offset (str): identifier of the row, from which the event data was
          extracted.
      places_title (str): places title.
      query (str): SQL query that was used to obtain the event data.
      title (str): title of the bookmark folder.
      url (str): bookmarked URL.
      visit_count (int): visit count.
    """

    DATA_TYPE = "firefox:places:bookmark"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.added_time = None
        self.bookmark_type = None
        self.hostname = None
        self.modification_time = None
        self.offset = None
        self.places_title = None
        self.query = None
        self.title = None
        self.url = None
        self.visit_count = None


class FirefoxPlacesBookmarkAnnotationEventData(events.EventData):
    """Firefox bookmark annotation event data.

    Attributes:
      added_time (dfdatetime.DateTimeValues): date and time the bookmark
          annotation was added.
      content (str): annotation content.
      modification_time (dfdatetime.DateTimeValues): date and time the bookmark
          annotation was last modified.
      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.
      title (str): title of the bookmark folder.
      url (str): bookmarked URL.
    """

    DATA_TYPE = "firefox:places:bookmark_annotation"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.added_time = None
        self.content = None
        self.modification_time = None
        self.offset = None
        self.query = None
        self.title = None
        self.url = None


class FirefoxPlacesBookmarkFolderEventData(events.EventData):
    """Firefox bookmark folder event data.

    Attributes:
      added_time (dfdatetime.DateTimeValues): date and time the bookmark folder
          was added.
      modification_time (dfdatetime.DateTimeValues): date and time the bookmark
          folder was last modified.
      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.
      title (str): title of the bookmark folder.
    """

    DATA_TYPE = "firefox:places:bookmark_folder"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.added_time = None
        self.modification_time = None
        self.offset = None
        self.query = None
        self.title = None


class FirefoxPlacesPageVisitedEventData(events.EventData):
    """Firefox page visited event data.

    Attributes:
      from_visit (str): URL that referred to the visited page.
      hidden (str): value to indicated if the URL was hidden.
      hostname (str): name of the visited host or domain.
      last_visited_time (dfdatetime.DateTimeValues): date and time the URL was
          last visited.
      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.
      title (str): title of the visited page.
      typed (str): value to indicated if the URL was typed.
      url (str): URL of the visited page.
      visit_count (int): visit count.
      visit_type (str): transition type for the event.
    """

    DATA_TYPE = "firefox:places:page_visited"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.from_visit = None
        self.hidden = None
        self.hostname = None
        self.last_visited_time = None
        self.offset = None
        self.query = None
        self.title = None
        self.typed = None
        self.url = None
        self.visit_count = None
        self.visit_type = None


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

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

    NAME = "firefox_history"
    DATA_FORMAT = "Mozilla Firefox history SQLite database (places.sqlite) file"

    REQUIRED_STRUCTURE = {
        "moz_places": frozenset(
            ["url", "title", "visit_count", "rev_host", "hidden", "typed", "id"]
        ),
        "moz_historyvisits": frozenset(
            ["id", "visit_date", "from_visit", "visit_type", "place_id"]
        ),
        "moz_bookmarks": frozenset(
            ["type", "title", "dateAdded", "lastModified", "id", "fk"]
        ),
        "moz_items_annos": frozenset(
            ["content", "dateAdded", "lastModified", "id", "item_id"]
        ),
    }

    QUERIES = [
        (
            (
                "SELECT moz_historyvisits.id, moz_places.url, moz_places.title, "
                "moz_places.visit_count, moz_historyvisits.visit_date, "
                "moz_historyvisits.from_visit, moz_places.rev_host, "
                "moz_places.hidden, moz_places.typed, moz_historyvisits.visit_type "
                "FROM moz_places, moz_historyvisits "
                "WHERE moz_places.id = moz_historyvisits.place_id"
            ),
            "ParsePageVisitedRow",
        ),
        (
            (
                "SELECT moz_bookmarks.type, moz_bookmarks.title AS bookmark_title, "
                "moz_bookmarks.dateAdded, moz_bookmarks.lastModified, "
                "moz_places.url, moz_places.title AS places_title, "
                "moz_places.rev_host, moz_places.visit_count, moz_bookmarks.id "
                "FROM moz_places, moz_bookmarks "
                "WHERE moz_bookmarks.fk = moz_places.id AND moz_bookmarks.type <> 3"
            ),
            "ParseBookmarkRow",
        ),
        (
            (
                "SELECT moz_items_annos.content, moz_items_annos.dateAdded, "
                "moz_items_annos.lastModified, moz_bookmarks.title, "
                "moz_places.url, moz_places.rev_host, moz_items_annos.id "
                "FROM moz_items_annos, moz_bookmarks, moz_places "
                "WHERE moz_items_annos.item_id = moz_bookmarks.id "
                "AND moz_bookmarks.fk = moz_places.id"
            ),
            "ParseBookmarkAnnotationRow",
        ),
        (
            (
                "SELECT moz_bookmarks.id, moz_bookmarks.title,"
                "moz_bookmarks.dateAdded, moz_bookmarks.lastModified "
                "FROM moz_bookmarks WHERE moz_bookmarks.type = 2"
            ),
            "ParseBookmarkFolderRow",
        ),
    ]

    _SCHEMA_V24 = {
        "moz_anno_attributes": (
            "CREATE TABLE moz_anno_attributes ( id INTEGER PRIMARY KEY, name "
            "VARCHAR(32) UNIQUE NOT NULL)"
        ),
        "moz_annos": (
            "CREATE TABLE moz_annos ( id INTEGER PRIMARY KEY, place_id INTEGER "
            "NOT NULL, anno_attribute_id INTEGER, mime_type VARCHAR(32) DEFAULT "
            "NULL, content LONGVARCHAR, flags INTEGER DEFAULT 0, expiration "
            "INTEGER DEFAULT 0, type INTEGER DEFAULT 0, dateAdded INTEGER "
            "DEFAULT 0, lastModified INTEGER DEFAULT 0)"
        ),
        "moz_bookmarks": (
            "CREATE TABLE moz_bookmarks ( id INTEGER PRIMARY KEY, type INTEGER, "
            "fk INTEGER DEFAULT NULL, parent INTEGER, position INTEGER, title "
            "LONGVARCHAR, keyword_id INTEGER, folder_type TEXT, dateAdded "
            "INTEGER, lastModified INTEGER)"
        ),
        "moz_bookmarks_roots": (
            "CREATE TABLE moz_bookmarks_roots ( root_name VARCHAR(16) UNIQUE, "
            "folder_id INTEGER)"
        ),
        "moz_favicons": (
            "CREATE TABLE moz_favicons ( id INTEGER PRIMARY KEY, url "
            "LONGVARCHAR UNIQUE, data BLOB, mime_type VARCHAR(32), expiration "
            "LONG)"
        ),
        "moz_historyvisits": (
            "CREATE TABLE moz_historyvisits ( id INTEGER PRIMARY KEY, "
            "from_visit INTEGER, place_id INTEGER, visit_date INTEGER, "
            "visit_type INTEGER, session INTEGER)"
        ),
        "moz_inputhistory": (
            "CREATE TABLE moz_inputhistory ( place_id INTEGER NOT NULL, input "
            "LONGVARCHAR NOT NULL, use_count INTEGER, PRIMARY KEY (place_id, "
            "input))"
        ),
        "moz_items_annos": (
            "CREATE TABLE moz_items_annos ( id INTEGER PRIMARY KEY, item_id "
            "INTEGER NOT NULL, anno_attribute_id INTEGER, mime_type VARCHAR(32) "
            "DEFAULT NULL, content LONGVARCHAR, flags INTEGER DEFAULT 0, "
            "expiration INTEGER DEFAULT 0, type INTEGER DEFAULT 0, dateAdded "
            "INTEGER DEFAULT 0, lastModified INTEGER DEFAULT 0)"
        ),
        "moz_keywords": (
            "CREATE TABLE moz_keywords ( id INTEGER PRIMARY KEY AUTOINCREMENT, "
            "keyword TEXT UNIQUE)"
        ),
        "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, favicon_id INTEGER, frecency INTEGER DEFAULT "
            "-1 NOT NULL, last_visit_date INTEGER )"
        ),
    }

    _SCHEMA_V25 = {
        "moz_anno_attributes": (
            "CREATE TABLE moz_anno_attributes ( id INTEGER PRIMARY KEY, name "
            "VARCHAR(32) UNIQUE NOT NULL)"
        ),
        "moz_annos": (
            "CREATE TABLE moz_annos ( id INTEGER PRIMARY KEY, place_id INTEGER "
            "NOT NULL, anno_attribute_id INTEGER, mime_type VARCHAR(32) DEFAULT "
            "NULL, content LONGVARCHAR, flags INTEGER DEFAULT 0, expiration "
            "INTEGER DEFAULT 0, type INTEGER DEFAULT 0, dateAdded INTEGER "
            "DEFAULT 0, lastModified INTEGER DEFAULT 0)"
        ),
        "moz_bookmarks": (
            "CREATE TABLE moz_bookmarks ( id INTEGER PRIMARY KEY, type INTEGER, "
            "fk INTEGER DEFAULT NULL, parent INTEGER, position INTEGER, title "
            "LONGVARCHAR, keyword_id INTEGER, folder_type TEXT, dateAdded "
            "INTEGER, lastModified INTEGER, guid TEXT)"
        ),
        "moz_bookmarks_roots": (
            "CREATE TABLE moz_bookmarks_roots ( root_name VARCHAR(16) UNIQUE, "
            "folder_id INTEGER)"
        ),
        "moz_favicons": (
            "CREATE TABLE moz_favicons ( id INTEGER PRIMARY KEY, url "
            "LONGVARCHAR UNIQUE, data BLOB, mime_type VARCHAR(32), expiration "
            "LONG, guid TEXT)"
        ),
        "moz_historyvisits": (
            "CREATE TABLE moz_historyvisits ( id INTEGER PRIMARY KEY, "
            "from_visit INTEGER, place_id INTEGER, visit_date INTEGER, "
            "visit_type INTEGER, session INTEGER)"
        ),
        "moz_hosts": (
            "CREATE TABLE moz_hosts ( id INTEGER PRIMARY KEY, host TEXT NOT "
            "NULL UNIQUE, frecency INTEGER, typed INTEGER NOT NULL DEFAULT 0, "
            "prefix TEXT)"
        ),
        "moz_inputhistory": (
            "CREATE TABLE moz_inputhistory ( place_id INTEGER NOT NULL, input "
            "LONGVARCHAR NOT NULL, use_count INTEGER, PRIMARY KEY (place_id, "
            "input))"
        ),
        "moz_items_annos": (
            "CREATE TABLE moz_items_annos ( id INTEGER PRIMARY KEY, item_id "
            "INTEGER NOT NULL, anno_attribute_id INTEGER, mime_type VARCHAR(32) "
            "DEFAULT NULL, content LONGVARCHAR, flags INTEGER DEFAULT 0, "
            "expiration INTEGER DEFAULT 0, type INTEGER DEFAULT 0, dateAdded "
            "INTEGER DEFAULT 0, lastModified INTEGER DEFAULT 0)"
        ),
        "moz_keywords": (
            "CREATE TABLE moz_keywords ( id INTEGER PRIMARY KEY AUTOINCREMENT, "
            "keyword TEXT UNIQUE)"
        ),
        "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, favicon_id INTEGER, frecency INTEGER DEFAULT "
            "-1 NOT NULL, last_visit_date INTEGER , guid TEXT)"
        ),
        "sqlite_stat1": ("CREATE TABLE sqlite_stat1(tbl, idx, stat)"),
    }

    SCHEMAS = [_SCHEMA_V24, _SCHEMA_V25]

    URL_CACHE_QUERY = (
        "SELECT h.id AS id, p.url, p.rev_host FROM moz_places p, "
        "moz_historyvisits h WHERE p.id = h.place_id"
    )

    def _GetUrl(self, url_id, cache, database):
        """Retrieves a URL from a reference to an entry in the from_visit table.

        Args:
          url_id (str): identifier of the visited URL.
          cache (SQLiteCache): cache which contains cached results from querying the
              moz_historyvisits table.
          database (SQLiteDatabase): database.

        Returns:
          str: URL and hostname.
        """
        url_cache_results = cache.GetResults("url")
        if not url_cache_results:
            result_set = database.Query(self.URL_CACHE_QUERY)

            cache.CacheQueryResults(result_set, "url", "id", ("url", "rev_host"))
            url_cache_results = cache.GetResults("url")

        url, reversed_hostname = url_cache_results.get(url_id, ["", ""])

        if not url:
            return ""

        hostname = self._ReverseHostname(reversed_hostname)
        return f"{url:s} ({hostname:s})"

    def _ReverseHostname(self, reversed_hostname):
        """Reverses the hostname and strips the leading dot.

        For example the reversed hostname "moc.elgoog.www." is converted into
        "www.google.com".

        Args:
          reversed_hostname (str): reversed hostname.

        Returns:
          str: hostname without a leading dot.
        """
        if not reversed_hostname:
            return ""

        if len(reversed_hostname) <= 1:
            return reversed_hostname

        if reversed_hostname[-1] == ".":
            return reversed_hostname[::-1][1:]

        return reversed_hostname[::-1][0:]

    def ParseBookmarkAnnotationRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a bookmark annotation 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 = FirefoxPlacesBookmarkAnnotationEventData()
        event_data.added_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "dateAdded"
        )
        event_data.content = self._GetRowValue(query_hash, row, "content")
        event_data.modification_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "lastModified"
        )
        event_data.offset = self._GetRowValue(query_hash, row, "id")
        event_data.query = query
        event_data.title = self._GetRowValue(query_hash, row, "title")
        event_data.url = self._GetRowValue(query_hash, row, "url")

        parser_mediator.ProduceEventData(event_data)

    def ParseBookmarkFolderRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a bookmark folder 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)

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

        event_data = FirefoxPlacesBookmarkFolderEventData()
        event_data.added_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "dateAdded"
        )
        event_data.modification_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "lastModified"
        )
        event_data.offset = self._GetRowValue(query_hash, row, "id")
        event_data.query = query
        event_data.title = title or "N/A"

        parser_mediator.ProduceEventData(event_data)

    def ParseBookmarkRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a bookmark 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 = FirefoxPlacesBookmarkEventData()
        event_data.added_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "dateAdded"
        )
        event_data.bookmark_type = self._GetRowValue(query_hash, row, "type")
        event_data.hostname = self._GetRowValue(query_hash, row, "rev_host")
        event_data.modification_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "lastModified"
        )
        event_data.offset = self._GetRowValue(query_hash, row, "id")
        event_data.places_title = self._GetRowValue(query_hash, row, "places_title")
        event_data.query = query
        event_data.title = self._GetRowValue(query_hash, row, "bookmark_title")
        event_data.url = self._GetRowValue(query_hash, row, "url")
        event_data.visit_count = self._GetRowValue(query_hash, row, "visit_count")

        parser_mediator.ProduceEventData(event_data)

    def ParsePageVisitedRow(
        self, parser_mediator, query, row, cache=None, database=None, **unused_kwargs
    ):
        """Parses a page visited 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.
          cache (Optional[SQLiteCache]): cache which contains cached results from
              querying the moz_historyvisits table.
          database (Optional[SQLiteDatabase]): database.
        """
        query_hash = hash(query)

        from_visit = self._GetRowValue(query_hash, row, "from_visit")
        if from_visit is not None:
            from_visit = self._GetUrl(from_visit, cache, database)

        reversed_hostname = self._GetRowValue(query_hash, row, "rev_host")

        event_data = FirefoxPlacesPageVisitedEventData()
        event_data.from_visit = from_visit
        event_data.hidden = self._GetRowValue(query_hash, row, "hidden")
        event_data.hostname = self._ReverseHostname(reversed_hostname)
        event_data.last_visited_time = self._GetPosixTimeInMicrosecondsRowValue(
            query_hash, row, "visit_date"
        )
        event_data.offset = self._GetRowValue(query_hash, row, "id")
        event_data.query = query
        event_data.title = self._GetRowValue(query_hash, row, "title")
        event_data.typed = self._GetRowValue(query_hash, row, "typed")
        event_data.url = self._GetRowValue(query_hash, row, "url")
        event_data.visit_count = self._GetRowValue(query_hash, row, "visit_count")
        event_data.visit_type = self._GetRowValue(query_hash, row, "visit_type")

        parser_mediator.ProduceEventData(event_data)


sqlite.SQLiteParser.RegisterPlugin(FirefoxHistoryPlugin)
