"""SQLite parser plugin for Windows 10 push notification database files."""

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


class WindowsPushNotificationEventData(events.EventData):
    """Windows push notification event data.

    Attributes:
      arrival_time (dfdatetime.DateTimeValues): date and time the push notification was
          received.
      boot_time (dfdatetime.DateTimeValues): date and time the of the last boot.
      expiration_time (dfdatetime.DateTimeValues): date and time the push notification
          expires.
      handler_identifier (str): identifier of the corresponding notification handler.
      notification_type (str): notification type.
      payload (dfdatetime.DateTimeValues): payload.
    """

    DATA_TYPE = "windows:push_notification:notification"

    def __init__(self):
        """Initialize event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.arrival_time = None
        self.boot_time = None
        self.expiration_time = None
        self.handler_identifier = None
        self.notification_type = None
        self.payload = None


class WindowsPushNotificationHandlerEventData(events.EventData):
    """Windows push notification handler event data.

    Attributes:
      creation_time (dfdatetime.DateTimeValues): date and time the push notification
          handler was created.
      handler_type (str): handler type.
      identifier (str): identifier.
      modification_time (dfdatetime.DateTimeValues): date and time the push notification
          handler was last modified.
      service_identifier (str): Windows Push Notification Service (WNS) identifier.
    """

    DATA_TYPE = "windows:push_notification:notification_handler"

    def __init__(self):
        """Initialize event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.creation_time = None
        self.handler_type = None
        self.identifier = None
        self.modification_time = None
        self.service_identifier = None


class WindowsPushNotificationPlugin(interface.SQLitePlugin):
    """SQLite parser plugin for Windows 10 push notification database files.

    The Windows 10 push notification database file is typically stored in:
    %APPDATA%\\Local\\Microsoft\\Windows\\Notifications\\wpndatabase.db
    """

    NAME = "windows_push_notification"
    DATA_FORMAT = "Windows 10 push notification SQLite database (wpndatabase.db) file"

    REQUIRED_STRUCTURE = {
        "Metadata": frozenset(["Key", "Value"]),
        "Notification": frozenset(
            [
                "ActivityId",
                "ArrivalTime",
                "BootId",
                "DataVersion",
                "ExpiresOnReboot",
                "ExpiryTime",
                "HandlerId",
                "Group",
                "Id",
                "Order",
                "Payload",
                "PayloadType",
                "Tag",
                "Type",
            ]
        ),
        "NotificationHandler": frozenset(
            [
                "ContainerSid",
                "CreatedTime",
                "HandlerType",
                "ModifiedTime",
                "ParentId",
                "PrimaryId",
                "RecordId",
                "SystemDataPropertySet",
                "WNFEventName",
                "WNSId",
            ]
        ),
    }

    QUERIES = [
        (
            (
                "SELECT RecordId, PrimaryId, WNSId, HandlerType, WNFEventName, "
                "SystemDataPropertySet, CreatedTime, ModifiedTime, ParentId, "
                "ContainerSid FROM NotificationHandler"
            ),
            "ParseNotificationHandlerRow",
        ),
        # Note that Order and Group must be escaped other they are interpreted
        # as SQL keywords.
        (
            (
                'SELECT "Order", Id, ActivityId, Type, Payload, Tag, "Group", '
                "ExpiryTime, ArrivalTime, DataVersion, PayloadType, BootId, "
                "ExpiresOnReboot, PrimaryId FROM Notification, NotificationHandler "
                "WHERE Notification.HandlerId == NotificationHandler.RecordId"
            ),
            "ParseNotificationRow",
        ),
    ]

    SCHEMAS = [
        {
            "HandlerAssets": (
                "CREATE TABLE [HandlerAssets]( [HandlerId] INTEGER "
                "CONSTRAINT[AssetsToHandler] "
                "REFERENCES[NotificationHandler]([RecordId]) ON DELETE CASCADE ON "
                "UPDATE CASCADE, [AssetKey] TEXT NOT NULL, [AssetValue] TEXT, "
                "CONSTRAINT[] PRIMARY KEY([AssetKey], [HandlerId]) ON CONFLICT "
                "REPLACE)"
            ),
            "HandlerSettings": (
                "CREATE TABLE [HandlerSettings]( [HandlerId] INTEGER "
                "CONSTRAINT[SettingsToHandler] "
                "REFERENCES[NotificationHandler]([RecordId]) ON DELETE CASCADE ON "
                "UPDATE CASCADE, [SettingKey] TEXT NOT NULL, [Value] INT, "
                "CONSTRAINT[] PRIMARY KEY([SettingKey], [HandlerId]) ON CONFLICT "
                "REPLACE)"
            ),
            "Metadata": (
                "CREATE TABLE [Metadata]( [Key] TEXT, [Value] INT64, CONSTRAINT[] "
                "PRIMARY KEY([Key]) ON CONFLICT REPLACE)"
            ),
            "Notification": (
                "CREATE TABLE [Notification]( [Order] INTEGER NOT NULL PRIMARY KEY, "
                "[Id] INTEGER NOT NULL, [HandlerId] INTEGER "
                "CONSTRAINT[NotificationToHandler] "
                "REFERENCES[NotificationHandler]([RecordId]) ON DELETE CASCADE ON "
                "UPDATE CASCADE, [ActivityId] GUID,[Type] TEXT NOT NULL, [Payload] "
                "BLOB, [Tag] TEXT, [Group] TEXT, [ExpiryTime] INT64, [ArrivalTime] "
                "INT64, [DataVersion] INT64 DEFAULT '0', [PayloadType] TEXT NOT "
                "NULL, [BootId] INT64 DEFAULT '0', [ExpiresOnReboot] BOOLEAN "
                "DEFAULT 'FALSE', UNIQUE([Id]) ON CONFLICT REPLACE)"
            ),
            "NotificationData": (
                "CREATE TABLE [NotificationData]( [NotificationId] INTEGER "
                "CONSTRAINT[DataToNotification] REFERENCES[Notification]([Id]) ON "
                "DELETE CASCADE ON UPDATE CASCADE, [Key] TEXT NOT NULL, [Value] "
                "TEXT, CONSTRAINT[] PRIMARY KEY([Key], [NotificationId]) ON "
                "CONFLICT REPLACE)"
            ),
            "NotificationHandler": (
                "CREATE TABLE [NotificationHandler] ( [RecordId] INTEGER PRIMARY "
                "KEY, [PrimaryId] TEXT NOT NULL COLLATE NOCASE, [WNSId] TEXT "
                "COLLATE NOCASE, [HandlerType] TEXT, [WNFEventName] INT64, "
                "[SystemDataPropertySet] BLOB, [CreatedTime] DATETIME, "
                "[ModifiedTime] DATETIME, [ParentId] TEXT COLLATE NOCASE, "
                "[ContainerSid] TEXT COLLATE NOCASE)"
            ),
            "TransientTable": (
                "CREATE TABLE [TransientTable]( [OfflineCacheCount] INTEGER, "
                "[NotificationId] INTEGER CONSTRAINT[TransientToNotification] "
                "REFERENCES[Notification]([Id]) ON DELETE CASCADE ON UPDATE "
                "CASCADE, [OfflineBundleId] TEXT, [ServerCacheRollover] BOOLEAN "
                "DEFAULT 'FALSE', [CrossDeviceMatchId] TEXT, [SuppressPopup] "
                "BOOLEAN DEFAULT 'FALSE', [IsMirroringDisabled] BOOLEAN DEFAULT "
                "'FALSE', [RecurrenceId] GUID, [MessageId] GUID, [Priority] "
                "INTEGER NOT NULL, [CV] TEXT)"
            ),
            "WNSPushChannel": (
                "CREATE TABLE [WNSPushChannel]( [ChannelId] TEXT NOT NULL, "
                "[HandlerId] INTEGER REFERENCES[NotificationHandler]([RecordId]) ON "
                "DELETE CASCADE ON UPDATE CASCADE, [Uri] TEXT, [ExpiryTime] INT64, "
                "[CreatedTime] INT64, [DeviceVersion] INT64 DEFAULT '0', "
                "CONSTRAINT[] PRIMARY KEY([ChannelId]) ON CONFLICT REPLACE)"
            ),
        }
    ]

    def ParseNotificationHandlerRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a row of the NotificationHandler table.

        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 = WindowsPushNotificationHandlerEventData()
        event_data.creation_time = self._GetDateTimeStringRowValue(
            query_hash, row, "CreatedTime"
        )
        event_data.handler_type = self._GetRowValue(query_hash, row, "HandlerType")
        event_data.identifier = self._GetRowValue(query_hash, row, "PrimaryId")
        event_data.modification_time = self._GetDateTimeStringRowValue(
            query_hash, row, "ModifiedTime"
        )
        event_data.service_identifier = self._GetRowValue(query_hash, row, "WNSId")

        # TODO: add support for WNFEventName

        parser_mediator.ProduceEventData(event_data)

    def ParseNotificationRow(self, parser_mediator, query, row, **unused_kwargs):
        """Parses a row of the Notification table.

        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)

        corrupted = True

        payload = None
        payload_type = self._GetRowValue(query_hash, row, "PayloadType")

        if payload_type.lower() != "xml":
            parser_mediator.ProduceWarning(
                f"unsupported payload type: {payload_type:s}"
            )
        else:
            payload = self._GetRowValue(query_hash, row, "Payload")

            try:
                payload = payload.decode("utf-8")
            except UnicodeDecodeError:
                parser_mediator.ProduceWarning(
                    "unable to decode payload as UTF-8. Unsupported code points are "
                    "escaped."
                )
                payload = payload.decode("utf-8", errors="backslashreplace")
                corrupted = True

            # TODO: parse payload

        event_data = WindowsPushNotificationEventData()
        event_data.arrival_time = self._GeFiletimeRowValue(
            query_hash, row, "ArrivalTime"
        )
        event_data.boot_time = self._GeFiletimeRowValue(query_hash, row, "BootId")
        event_data.expiration_time = self._GeFiletimeRowValue(
            query_hash, row, "ExpiryTime"
        )
        event_data.handler_identifier = self._GetRowValue(query_hash, row, "PrimaryId")
        event_data.notification_type = self._GetRowValue(query_hash, row, "Type")
        event_data.payload = payload

        parser_mediator.ProduceEventData(event_data, corrupted=corrupted)


sqlite.SQLiteParser.RegisterPlugin(WindowsPushNotificationPlugin)
