import uuid
from datetime import datetime
import pytest
from sqlalchemy.orm import sessionmaker
from starlette.testclient import WebSocketDisconnect
from tests.conftest import make_project
from backend import portal_auth as pa
from backend.auth_passwords import hash_password
from backend.models import MonitoringLocation


def _sound_location(db_session, project):
    loc = MonitoringLocation(
        id=str(uuid.uuid4()), project_id=project.id, name="Site",
        location_type="sound", created_at=datetime.utcnow(),
        sort_order=0)
    db_session.add(loc)
    db_session.commit()
    return loc


def test_session_for_A_cannot_open_B_location(client, db_session):
    a = make_project(db_session, portal_enabled=True, portal_link_token="ta",
                     portal_password_hash=hash_password("pw"))
    b = make_project(db_session)
    b_loc = _sound_location(db_session, b)

    # Establish an A session
    r = client.post("/portal/p/ta", data={"password": "pw"}, follow_redirects=False)
    assert r.status_code == 303

    # Try to open B's location page → 404 (not 403), no leak
    r2 = client.get(f"/portal/location/{b_loc.id}")
    assert r2.status_code == 404


def test_session_can_open_its_own_location(client, db_session):
    # Positive case: proves the negative test's 404 is real scoping, not a blanket
    # "client owns nothing" failure — an A session CAN open A's own location.
    a = make_project(db_session, portal_enabled=True, portal_link_token="ta2",
                     portal_password_hash=hash_password("pw"))
    a_loc = _sound_location(db_session, a)
    r = client.post("/portal/p/ta2", data={"password": "pw"}, follow_redirects=False)
    assert r.status_code == 303
    r2 = client.get(f"/portal/location/{a_loc.id}")
    assert r2.status_code == 200


def test_ws_stream_rejects_unauthenticated(client, db_session):
    # The live-feed WebSocket must refuse a connection with no session cookie (1008).
    a = make_project(db_session, portal_enabled=True, portal_link_token="tw1",
                     portal_password_hash=hash_password("pw"))
    a_loc = _sound_location(db_session, a)
    with pytest.raises(WebSocketDisconnect) as exc:
        with client.websocket_connect(f"/portal/api/location/{a_loc.id}/stream") as ws:
            ws.receive_text()
    assert exc.value.code == 1008


def test_ws_stream_rejects_cross_project(client, db_session, monkeypatch):
    # The WebSocket enforces the SAME per-project ownership as the HTTP routes: a
    # B-session opening A's stream is closed 1008 (ownership) before any device feed.
    # The handler uses SessionLocal() directly (not the get_db override), so point it
    # at the test DB engine so this genuinely exercises the ownership check (not a
    # vacuous "client not found").
    import backend.routers.portal as portal_router
    monkeypatch.setattr(portal_router, "SessionLocal",
                        sessionmaker(bind=db_session.get_bind()))

    a = make_project(db_session, portal_enabled=True, portal_link_token="tw2",
                     portal_password_hash=hash_password("pw"))
    a_loc = _sound_location(db_session, a)
    make_project(db_session, portal_enabled=True, portal_link_token="tw3",
                 portal_password_hash=hash_password("pw"))
    # Log in as project B, then aim the stream at project A's location.
    assert client.post("/portal/p/tw3", data={"password": "pw"},
                       follow_redirects=False).status_code == 303
    with pytest.raises(WebSocketDisconnect) as exc:
        with client.websocket_connect(f"/portal/api/location/{a_loc.id}/stream") as ws:
            ws.receive_text()
    assert exc.value.code == 1008
