import typing as t

from lavalink_rs.model.player import ConnectionInfo, Player, Filters
from lavalink_rs.model.http import UpdatePlayer, Info, ResumingState
from lavalink_rs.model.track import TrackData, Track
from lavalink_rs.model.events import (
    Stats,
    PlayerUpdate,
    TrackStart,
    TrackEnd,
    TrackException,
    TrackStuck,
    WebSocketClosed,
    Ready,
)

__CD = t.TypeVar("__CD")
__PD = t.TypeVar("__PD")


class LavalinkClient:
    data: t.Optional[__CD]

    @staticmethod
    async def new(
        events: EventHandler,
        nodes: t.List[NodeBuilder],
        strategy: NodeDistributionStrategy,
        data: t.Optional[__CD] = None,
    ) -> LavalinkClient: ...
    def get_player_context(
        self, guild_id: t.Union[GuildId, int]
    ) -> t.Optional[PlayerContext]: ...
    def get_node_by_index(self, idx: int) -> t.Optional[Node]: ...
    async def get_node_for_guild(self, guild_id: t.Union[GuildId, int]) -> Node: ...
    async def create_player(
        self,
        guild_id: t.Union[GuildId, int],
        endpoint: str,
        token: str,
        session_id: str,
        channel_id: ChannelId,
    ) -> Player: ...
    async def create_player_context(
        self,
        guild_id: t.Union[GuildId, int],
        endpoint: str,
        token: str,
        session_id: str,
        channel_id: ChannelId,
        data: t.Optional[__PD] = None,
    ) -> PlayerContext: ...
    async def delete_player(self, guild_id: t.Union[GuildId, int]) -> None: ...
    async def delete_all_player_contexts(self) -> None: ...
    async def update_player(
        self,
        guild_id: t.Union[GuildId, int],
        update_player: UpdatePlayer,
        no_replace: bool,
    ) -> Player: ...
    async def load_tracks(
        self, guild_id: t.Union[GuildId, int], identifier: str
    ) -> Track: ...
    async def decode_track(
        self, guild_id: t.Union[GuildId, int], track: str
    ) -> TrackData: ...
    async def decode_tracks(
        self, guild_id: t.Union[GuildId, int], tracks: t.List[str]
    ) -> t.List[TrackData]: ...
    async def request_version(self, guild_id: t.Union[GuildId, int]) -> str: ...
    async def request_stats(self, guild_id: t.Union[GuildId, int]) -> Stats: ...
    async def request_info(self, guild_id: t.Union[GuildId, int]) -> Info: ...
    async def request_player(self, guild_id: t.Union[GuildId, int]) -> Player: ...
    async def request_all_players(
        self, guild_id: t.Union[GuildId, int]
    ) -> t.List[Player]: ...
    def handle_voice_server_update(
        self, guild_id: t.Union[GuildId, int], token: str, endpoint: t.Optional[str]
    ) -> None: ...
    def handle_voice_state_update(
        self,
        guild_id: t.Union[GuildId, int],
        channel_id: t.Optional[t.Union[ChannelId, int]],
        user_id: t.Union[UserId, int],
        session_id: str,
    ) -> None: ...
    async def get_connection_info(
        self, guild_id: t.Union[GuildId, int], timeout: int
    ) -> ConnectionInfo: ...


class PlayerContext:
    data: t.Optional[__PD]

    def close(self) -> None: ...
    def skip(self) -> None: ...
    def finish(self, should_continue: bool) -> None: ...
    def update_player_data(self, player: Player) -> None: ...
    async def get_player(self) -> Player: ...
    async def update_player(
        self, update_player: UpdatePlayer, no_replace: bool
    ) -> Player: ...
    async def play(self, track: TrackData) -> Player: ...
    async def play_now(self, track: TrackData) -> Player: ...
    async def stop_now(self) -> Player: ...
    async def set_pause(self, pause: bool) -> Player: ...
    async def set_volume(self, volume: int) -> Player: ...
    async def set_filters(self, filters: Filters) -> Player: ...
    async def set_position_ms(self, position: int) -> Player: ...
    def queue(self, track: t.Union[TrackInQueue, TrackData]) -> None: ...
    def get_queue(self) -> QueueRef: ...


class NodeBuilder:
    hostname: str
    is_ssl: bool
    password: str
    user_id: UserId
    session_id: t.Optional[str]

    def __init__(
        self,
        hostname: str,
        is_ssl: bool,
        password: str,
        user_id: t.Union[UserId, int],
        session_id: t.Optional[str] = None,
        events: t.Optional[EventHandler] = None,
    ) -> None: ...


class EventHandler:
    async def stats(
        self, client: LavalinkClient, session_id: str, event: Stats
    ) -> None: ...
    async def player_update(
        self, client: LavalinkClient, session_id: str, event: PlayerUpdate
    ) -> None: ...
    async def track_start(
        self, client: LavalinkClient, session_id: str, event: TrackStart
    ) -> None: ...
    async def track_end(
        self, client: LavalinkClient, session_id: str, event: TrackEnd
    ) -> None: ...
    async def track_exception(
        self, client: LavalinkClient, session_id: str, event: TrackException
    ) -> None: ...
    async def track_stuck(
        self, client: LavalinkClient, session_id: str, event: TrackStuck
    ) -> None: ...
    async def websocket_closed(
        self, client: LavalinkClient, session_id: str, event: WebSocketClosed
    ) -> None: ...
    async def ready(
        self, client: LavalinkClient, session_id: str, event: Ready
    ) -> None: ...


class NodeDistributionStrategy:
    @staticmethod
    def new() -> NodeDistributionStrategy: ...
    @staticmethod
    def sharded() -> NodeDistributionStrategy: ...
    @staticmethod
    def round_robin() -> NodeDistributionStrategy: ...
    @staticmethod
    def main_fallback() -> NodeDistributionStrategy: ...
    @staticmethod
    def lowest_load() -> NodeDistributionStrategy: ...
    @staticmethod
    def highest_free_memory() -> NodeDistributionStrategy: ...
    @staticmethod
    def custom(
        func: t.Callable[[LavalinkClient, t.Union[GuildId, int]], t.Awaitable[Node]],
    ) -> NodeDistributionStrategy: ...


class Node:
    http: Http


class Http:
    authority: str
    rest_address: str
    rest_address_versionless: str

    async def delete_player(
        self,
        guild_id: t.Union[GuildId, int],
        session_id: str,
    ) -> None: ...
    async def update_player(
        self,
        guild_id: t.Union[GuildId, int],
        session_id: str,
        data: UpdatePlayer,
        no_replace: bool,
    ) -> Player: ...
    async def set_resuming_state(
        self,
        session_id: str,
        resuming_state: ResumingState,
    ) -> ResumingState: ...
    async def load_tracks(self, identifier: str) -> Track: ...
    async def version(self) -> str: ...
    async def stats(self) -> Stats: ...
    async def info(self) -> Info: ...
    async def decode_track(self, track: str) -> TrackData: ...
    async def decode_tracks(self, tracks: t.List[str]) -> t.List[TrackData]: ...
    async def get_player(
        self,
        guild_id: t.Union[GuildId, int],
        session_id: str,
    ) -> Player: ...
    async def get_players(self, session_id: str) -> t.List[Player]: ...


class QueueRef:
    async def get_queue(self) -> t.List[TrackInQueue]: ...
    async def get_track(self, index: int) -> t.Optional[TrackInQueue]: ...
    async def get_count(self) -> int: ...
    def push_to_back(self, track: t.Union[TrackInQueue, TrackData]) -> None: ...
    def push_to_front(self, track: t.Union[TrackInQueue, TrackData]) -> None: ...
    def insert(self, index: int, track: t.Union[TrackInQueue, TrackData]) -> None: ...
    def remove(self, index: int) -> None: ...
    def clear(self) -> None: ...
    def replace(self, tracks: t.Sequence[t.Union[TrackInQueue, TrackData]]) -> None: ...
    def append(self, tracks: t.Sequence[t.Union[TrackInQueue, TrackData]]) -> None: ...
    def swap(self, index: int, track: t.Union[TrackInQueue, TrackData]) -> None: ...


class TrackInQueue:
    track: TrackData
    volume: t.Optional[int]
    end_time_ms: t.Optional[int]
    start_time_ms: t.Optional[int]
    filters: t.Optional[Filters]


class GuildId:
    inner: int

    def __init__(self, id: int) -> None: ...


class ChannelId:
    inner: int

    def __init__(self, id: int) -> None: ...


class UserId:
    inner: int

    def __init__(self, id: int) -> None: ...
