# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2024 New Vector, Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.

from typing import Any, Iterator, Mapping

from synapse.synapse_rust.room_versions import RoomVersion
from synapse.types import JsonDict, JsonMapping, StrSequence
from synapse.util.duration import Duration

class EventInternalMetadata:
    def __init__(self, internal_metadata_dict: JsonDict): ...

    stream_ordering: int | None
    """the stream ordering of this event. None, until it has been persisted."""
    instance_name: str | None
    """the instance name of the server that persisted this event. None, until it has been persisted."""
    redacted_by: str | None
    """the event ID of the redaction event, if this event has been redacted. Set dynamically at load time, not persisted."""

    outlier: bool
    """whether this event is an outlier (ie, whether we have the state at that
    point in the DAG)"""

    out_of_band_membership: bool
    send_on_behalf_of: str
    recheck_redaction: bool
    soft_failed: bool
    proactively_send: bool
    redacted: bool

    policy_server_spammy: bool
    """whether the policy server indicated that this event is spammy"""

    spam_checker_spammy: bool
    """Whether a spam checker module indicated that this event is spammy

    Note that spam checkers also cause the event to be marked as soft-failed.

    This flags exists for two reasons:
        1. as debugging information
        2. to prevent the soft-failed re-evaluation of spammy events
           (the re-evaluation behaviour originates from MSC4354 Sticky Events)

    Note that historical spammy events won't have this flag.
    """

    txn_id: str
    """The transaction ID, if it was set when the event was created."""
    delay_id: str
    """The delay ID, set only if the event was a delayed event."""
    token_id: int
    """The access token ID of the user who sent this event, if any."""
    device_id: str
    """The device ID of the user who sent this event, if any."""

    # MSC4242 state dags
    calculated_auth_event_ids: list[str]

    def get_dict(self) -> JsonDict: ...
    def is_outlier(self) -> bool: ...
    def copy(self) -> "EventInternalMetadata": ...
    def is_out_of_band_membership(self) -> bool:
        """Whether this event is an out-of-band membership.

        OOB memberships are a special case of outlier events: they are membership events
        for federated rooms that we aren't full members of. Examples include invites
        received over federation, and rejections for such invites.

        The concept of an OOB membership is needed because these events need to be
        processed as if they're new regular events (e.g. updating membership state in
        the database, relaying to clients via /sync, etc) despite being outliers.

        See also https://element-hq.github.io/synapse/develop/development/room-dag-concepts.html#out-of-band-membership-events.

        (Added in synapse 0.99.0, so may be unreliable for events received before that)
        """

    def get_send_on_behalf_of(self) -> str | None:
        """Whether this server should send the event on behalf of another server.
        This is used by the federation "send_join" API to forward the initial join
        event for a server in the room.

        returns a str with the name of the server this event is sent on behalf of.
        """

    def need_to_check_redaction(self) -> bool:
        """Whether the redaction event needs to be rechecked when fetching
        from the database.

        Starting in room v3 redaction events are accepted up front, and later
        checked to see if the redacter and redactee's domains match.

        If the sender of the redaction event is allowed to redact any event
        due to auth rules, then this will always return false.
        """

    def is_soft_failed(self) -> bool:
        """Whether the event has been soft failed.

        Soft failed events should be handled as usual, except:
            1. They should not go down sync or event streams, or generally
               sent to clients.
            2. They should not be added to the forward extremities (and
               therefore not to current state).
        """

    def should_proactively_send(self) -> bool:
        """Whether the event, if ours, should be sent to other clients and
        servers.

        This is used for sending dummy events internally. Servers and clients
        can still explicitly fetch the event.
        """

    def is_redacted(self) -> bool:
        """Whether the event has been redacted.

        This is used for efficiently checking whether an event has been
        marked as redacted without needing to make another database call.
        """

    def is_notifiable(self) -> bool:
        """Whether this event can trigger a push notification"""

def event_visible_to_server(
    sender: str,
    target_server_name: str,
    history_visibility: str,
    erased_senders: Mapping[str, bool],
    partial_state_invisible: bool,
    memberships: list[tuple[str, str]],
) -> bool:
    """Determine whether the server is allowed to see the unredacted event.

    Args:
        sender: The sender of the event.
        target_server_name: The server we want to send the event to.
        history_visibility: The history_visibility value at the event.
        erased_senders: A mapping of users and whether they have requested erasure. If a
            user is not in the map, it is treated as though they haven't requested erasure.
        partial_state_invisible: Whether the event should be treated as invisible due to
            the partial state status of the room.
        memberships: A list of membership state information at the event for users
            matching the `target_server_name`. Each list item must contain a tuple of
            (state_key, membership).

    Returns:
        Whether the server is allowed to see the unredacted event.
    """

class Signatures:
    """A class representing the signatures on an event."""

    def __init__(self, signatures: Mapping[str, Mapping[str, str]] | None = None): ...
    def get_signature(self, server_name: str, key_id: str) -> str | None:
        """Get the signature for the given server name and key ID, if it exists."""

    def __getitem__(self, server_name: str) -> Mapping[str, str]:
        """Get the signatures for the given server name. Raises KeyError if there
        are no signatures for that server."""

    def __contains__(self, server_name: Any) -> bool:
        """Check if there are signatures for the given server name."""

    def __len__(self) -> int:
        """Return the number of servers that have signatures."""

    def add_signature(self, server_name: str, key_id: str, signature: str) -> None:
        """Add a signature for the given server name and key ID."""

    def update(self, signatures: Mapping[str, Mapping[str, str]]) -> None:
        """Update the signatures with the given signatures.

        Will overwrite all existing signatures for the server names provided.
        """

    def as_dict(self) -> dict[str, dict[str, str]]:
        """Return a copy of the signatures as a dictionary."""

class Unsigned:
    """A class representing the unsigned data of an event."""

    def __init__(self, unsigned_json: str): ...
    def __getitem__(self, key: str) -> Any:
        """Get the value for the given key.

        Raises KeyError if the key is unset or not recognised."""

    def __setitem__(self, key: str, value: Any) -> None:
        """Set the value for the given key.

        Raises KeyError if the key is not recognised."""

    def __delitem__(self, key: str) -> None:
        """Delete the value for the given key.

        Raises KeyError if the key is unset or not recognised."""

    def __contains__(self, key: Any) -> bool: ...
    def get(self, key: str, default: Any = None) -> Any:
        """Get the value for the given key, or ``default`` if the key is unset."""

    def for_persistence(self) -> JsonDict:
        """Return a dict of the fields that should be persisted to the database."""

    def for_event(self) -> JsonDict:
        """Return a dict of all unsigned fields, including those only kept in
        memory, suitable for inclusion in an event."""

class JsonObject(Mapping[str, Any]):
    """Immutable JSON object mapping."""

    def __init__(self, content_dict: JsonMapping | None = None): ...
    def __len__(self) -> int: ...
    def __getitem__(self, key: str) -> Any: ...
    def __iter__(self) -> Iterator[str]: ...
    def __eq__(self, other: object) -> bool: ...

class Event:
    """Represents a Matrix event."""

    def __init__(
        self,
        event_json: str,
        room_version: RoomVersion,
        internal_metadata_dict: JsonDict,
        rejected_reason: str | None,
    ) -> None: ...
    def get_dict(self) -> JsonDict:
        """Convert the event to a dictionary suitable for serialisation."""

    def get_dict_for_persistence(self) -> JsonDict:
        """Like ``get_dict``, but serializes ``unsigned`` in a form suitable for
        persistence."""

    def get_pdu_json(self, time_now: int | None = None) -> JsonDict:
        """Like ``get_dict``, but serializes ``unsigned`` in a form suitable
        for sending over federation."""

    def get_templated_pdu_json(self) -> JsonDict:
        """Like ``get_dict``, except strips fields like ``signatures``,
        ``hashes`` and ``unsigned`` so that the result is suitable as a template for
        creating new events. Used in make_{join,leave,knock} flows."""

    @property
    def event_id(self) -> str: ...
    @property
    def room_id(self) -> str: ...
    @property
    def signatures(self) -> Signatures: ...
    @property
    def content(self) -> JsonMapping: ...
    @property
    def depth(self) -> int: ...
    @property
    def hashes(self) -> dict[str, str]: ...
    @property
    def origin_server_ts(self) -> int: ...
    @property
    def sender(self) -> str: ...
    @property
    def state_key(self) -> str: ...
    @property
    def type(self) -> str: ...
    @property
    def unsigned(self) -> Unsigned: ...
    @property
    def internal_metadata(self) -> EventInternalMetadata: ...
    @property
    def rejected_reason(self) -> str | None: ...
    @property
    def room_version(self) -> RoomVersion: ...
    @property
    def format_version(self) -> int:
        """The EventFormatVersion implemented by this event."""

    @property
    def membership(self) -> Any: ...
    @property
    def redacts(self) -> Any | None: ...
    def prev_event_ids(self) -> StrSequence:
        """Returns the list of prev event IDs."""

    def auth_event_ids(self) -> StrSequence:
        """Returns the list of auth event IDs"""

    def is_state(self) -> bool: ...
    def get_state_key(self) -> str | None:
        """Get the state key of this event, or None if it's not a state event."""
    def __contains__(self, key: str) -> bool: ...
    def get(self, key: str, default: Any = None) -> Any: ...
    def items(self) -> list[tuple[str, Any]]: ...
    def keys(self) -> list[str]: ...
    def deep_copy(self) -> "Event":
        """Returns a deep copy of this object, such that modifying the copy will
        not affect the original."""

    def sticky_duration(self) -> Duration | None:
        """If this event has the ``msc4354_sticky`` top-level field, returns a
        ``SynapseDuration`` representing the sticky duration. Otherwise returns
        ``None``."""

def redact_event(event: Event) -> Event:
    """Returns a pruned version of the given event, which removes all keys we
    don't know about or think could potentially be dodgy.
    """

def redact_event_dict(room_version: RoomVersion, event_dict: JsonMapping) -> JsonDict:
    """Returns a pruned version of the given event dict, which removes all keys
    we don't know about or think could potentially be dodgy.

    Returns the redacted event as a dict.
    """
