"""The real LiveKit adapter's local parts: webhook signatures, pass attributes, and
what it asks LiveKit's room service to do (against a stand-in)."""

import base64
import hashlib
import json

import jwt
import pytest
from livekit import api
from livekit.api.twirp_client import ServerError

from app.livekit.adapter import InvalidWebhookError, LiveKitAdapter

KEY = "test-key"
SECRET = "test-livekit-secret-0123456789abcdef"


@pytest.fixture
async def adapter():
    adapter = LiveKitAdapter("http://livekit.test", KEY, SECRET)
    yield adapter
    await adapter.aclose()


def _signed(body: str, secret: str = SECRET) -> str:
    """What LiveKit puts in the webhook's Authorization header."""
    digest = base64.b64encode(hashlib.sha256(body.encode()).digest()).decode()
    return api.AccessToken(KEY, secret).with_sha256(digest).to_jwt()


def _body(event: str = "participant_joined") -> str:
    return json.dumps(
        {
            "event": event,
            "room": {"name": "k3x9-p2mq-7ad"},
            "participant": {"identity": "helixtahr:26:674:tab-1", "sid": "PA_abc"},
            "id": "EV_1",
            "createdAt": "1793728642",
        }
    )


async def test_reads_a_webhook_livekit_signed(adapter):
    body = _body()

    event = adapter.read_webhook(body, _signed(body))

    assert (event.event, event.room) == ("participant_joined", "k3x9-p2mq-7ad")
    assert (event.identity, event.connection_id) == ("helixtahr:26:674:tab-1", "PA_abc")


async def test_track_events_say_the_source(adapter):
    body = json.dumps(
        {
            "event": "track_unpublished",
            "room": {"name": "k3x9-p2mq-7ad"},
            "participant": {"identity": "helixtahr:26:674:tab-1", "sid": "PA_abc"},
            "track": {"sid": "TR_1", "source": "SCREEN_SHARE"},
        }
    )

    event = adapter.read_webhook(body, _signed(body))

    assert (event.event, event.track_source) == ("track_unpublished", "SCREEN_SHARE")


async def test_room_events_have_no_participant(adapter):
    body = json.dumps({"event": "room_finished", "room": {"name": "k3x9-p2mq-7ad"}})

    event = adapter.read_webhook(body, _signed(body))

    assert (event.event, event.identity) == ("room_finished", None)


@pytest.mark.parametrize(
    "authorization",
    [
        "",
        "not-a-token",
        _signed(_body(), secret="someone-elses-secret-0123456789abcd"),
        _signed(_body("participant_left")),  # signed for a different body
    ],
)
async def test_refuses_webhooks_livekit_didnt_sign(adapter, authorization):
    with pytest.raises(InvalidWebhookError):
        adapter.read_webhook(_body(), authorization)


async def test_pass_carries_the_attributes(adapter):
    token = adapter.make_pass(
        room="k3x9-p2mq-7ad",
        identity="helixtahr:26:674:tab-1",
        name="Test User",
        sources=["camera", "microphone"],
        connect_within_seconds=600,
        attributes={"role": "host", "user_id": "674"},
    )

    claims = jwt.decode(token, SECRET, algorithms=["HS256"])
    assert claims["attributes"] == {"role": "host", "user_id": "674"}


class _StubRoomService:
    """Answers like LiveKit's room service for one participant's tracks."""

    def __init__(self, tracks=None, missing=False):
        self.tracks = tracks or []
        self.missing = missing
        self.mute_requests = []

    async def get_participant(self, request):
        if self.missing:
            raise ServerError("not_found", "participant not found", status=404)
        return api.ParticipantInfo(identity=request.identity, tracks=self.tracks)

    async def mute_published_track(self, request):
        self.mute_requests.append((request.identity, request.track_sid, request.muted))
        return api.MuteRoomTrackResponse()


def _track(sid, source, muted=False):
    return api.TrackInfo(sid=sid, source=api.TrackSource.Value(source), muted=muted)


async def test_mute_microphone_mutes_only_a_sending_microphone(adapter, monkeypatch):
    # HEL-983: the camera, screen-share audio and an already muted mic are left alone.
    stub = _StubRoomService(
        [
            _track("TR_mic", "MICROPHONE"),
            _track("TR_old_mic", "MICROPHONE", muted=True),
            _track("TR_cam", "CAMERA"),
            _track("TR_share_audio", "SCREEN_SHARE_AUDIO"),
        ]
    )
    monkeypatch.setattr(type(adapter._lk), "room", property(lambda self: stub))

    assert await adapter.mute_microphone("k3x9-p2mq-7ad", "helixtahr:26:700:tab-1") is True
    assert stub.mute_requests == [("helixtahr:26:700:tab-1", "TR_mic", True)]


async def test_mute_microphone_of_someone_gone_is_nothing(adapter, monkeypatch):
    stub = _StubRoomService(missing=True)
    monkeypatch.setattr(type(adapter._lk), "room", property(lambda self: stub))

    assert await adapter.mute_microphone("k3x9-p2mq-7ad", "helixtahr:26:700:tab-1") is False
