"""The "owner controls adding their own data; anyone linked can mute it"
permission pattern, repeated across calendar-select, tasks-source, and
whiteboard-source -- exercised at the HTTP layer (not just unit-level)
since the whole point is verifying the *endpoint's* authorization check,
not just a helper function's logic."""

from __future__ import annotations

import time

from app.models import (
    CalendarWidgetConfig,
    Frame,
    FrameCalendar,
    User,
    Widget,
    WhiteboardWidgetConfig,
)

from .conftest import csrf_headers, link_user, login, make_user


def _setup_two_linked_users(client, db_session) -> Frame:
    """alice is the frame's admin/owner (via /setup); bob is a second
    user linked to the same frame #1 but neither owns nor controls it."""
    client.post("/setup", data={"username": "alice", "password": "hunter22"})
    bob = make_user(db_session, "bob")
    frame = db_session.get(Frame, 1)
    link_user(db_session, bob, frame)
    return frame


def _add_whiteboard_widget(db_session, frame: Frame) -> Widget:
    """Frame #1's auto-migrated widget is a photos widget (its mode was
    "photos" before the widget system existed) -- these tests need a
    whiteboard widget too, which nothing creates yet until the
    widget-placement UI (a later phase) ships, so it's added directly
    here the same way the widget unit tests do."""
    widget = Widget(frame_id=frame.id, widget_type="whiteboard", x=0, y=0, w=8, h=5,
                    sort_order=1, created_at=time.time())
    db_session.add(widget)
    db_session.flush()
    db_session.add(WhiteboardWidgetConfig(widget_id=widget.id))
    db_session.commit()
    return widget


def _add_calendar_widget(db_session, frame: Frame) -> Widget:
    widget = Widget(frame_id=frame.id, widget_type="calendar", x=0, y=0, w=8, h=5,
                    sort_order=1, created_at=time.time())
    db_session.add(widget)
    db_session.flush()
    db_session.add(CalendarWidgetConfig(widget_id=widget.id))
    db_session.commit()
    return widget


# --- whiteboard-source ---

def test_whiteboard_source_owner_can_set_it(client, db_session):
    frame = _setup_two_linked_users(client, db_session)
    widget = _add_whiteboard_widget(db_session, frame)
    # alice is still logged in from /setup
    resp = client.post("/api/frames/1/whiteboard-source", json={
        "url": "https://cloud.example.com/dav/files/alice/board.whiteboard",
    }, headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    cfg = db_session.get(WhiteboardWidgetConfig, widget.id)
    assert cfg.user_id is not None
    assert cfg.url == "https://cloud.example.com/dav/files/alice/board.whiteboard"


def test_whiteboard_source_set_always_targets_the_caller(client, db_session):
    """bob has no way to point the frame at someone else's account --
    there's no target-user field in the request at all, so a "set" call
    from bob always attaches to bob, even if he pastes alice's URL."""
    frame = _setup_two_linked_users(client, db_session)
    widget = _add_whiteboard_widget(db_session, frame)
    client.cookies.clear()
    login(client, "bob")

    resp = client.post("/api/frames/1/whiteboard-source", json={
        "url": "https://cloud.example.com/dav/files/alice/board.whiteboard",
    }, headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    bob_row = db_session.query(User).filter_by(username="bob").one()
    cfg = db_session.get(WhiteboardWidgetConfig, widget.id)
    assert cfg.user_id == bob_row.id


def test_whiteboard_source_anyone_linked_can_clear(client, db_session):
    frame = _setup_two_linked_users(client, db_session)
    widget = _add_whiteboard_widget(db_session, frame)
    client.post("/api/frames/1/whiteboard-source", json={"url": "https://cloud.example.com/board.whiteboard"},
                headers=csrf_headers(client))

    client.cookies.clear()
    login(client, "bob")
    resp = client.post("/api/frames/1/whiteboard-source", json={"url": None}, headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    cfg = db_session.get(WhiteboardWidgetConfig, widget.id)
    assert cfg.url == ""
    assert cfg.user_id is None


def test_whiteboard_source_rejects_non_http_url(client, db_session):
    frame = _setup_two_linked_users(client, db_session)
    _add_whiteboard_widget(db_session, frame)
    resp = client.post("/api/frames/1/whiteboard-source", json={"url": "javascript:alert(1)"},
                       headers=csrf_headers(client))
    assert resp.status_code == 400


def test_whiteboard_source_unlinked_user_cannot_touch_it(client, db_session):
    frame = _setup_two_linked_users(client, db_session)
    _add_whiteboard_widget(db_session, frame)
    make_user(db_session, "mallory")  # exists, but never linked to frame 1
    client.cookies.clear()
    login(client, "mallory")

    resp = client.post("/api/frames/1/whiteboard-source", json={"url": "https://x.example.com/b.whiteboard"},
                       headers=csrf_headers(client, "/settings"))
    assert resp.status_code == 404


def test_whiteboard_source_404s_when_frame_has_no_whiteboard_widget(client, db_session):
    """Distinct from the unlinked-user 404 above -- this is a linked,
    fully-permitted owner hitting the endpoint on a frame that simply
    doesn't have a whiteboard widget yet (frame #1's auto-migrated
    widget is a photos widget)."""
    _setup_two_linked_users(client, db_session)
    resp = client.post("/api/frames/1/whiteboard-source", json={"url": "https://x.example.com/b.whiteboard"},
                       headers=csrf_headers(client))
    assert resp.status_code == 404


# --- tasks-source ---

def test_tasks_source_set_always_targets_the_caller(client, db_session):
    frame = _setup_two_linked_users(client, db_session)
    widget = _add_calendar_widget(db_session, frame)
    client.cookies.clear()
    login(client, "bob")

    resp = client.post("/api/frames/1/tasks-source", json={"calendar_key": "caldav:/some/tasks/"},
                       headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    bob_row = db_session.query(User).filter_by(username="bob").one()
    cfg = db_session.get(CalendarWidgetConfig, widget.id)
    assert cfg.tasks_user_id == bob_row.id
    assert cfg.tasks_calendar_key == "caldav:/some/tasks/"


def test_tasks_source_anyone_linked_can_clear(client, db_session):
    frame = _setup_two_linked_users(client, db_session)
    widget = _add_calendar_widget(db_session, frame)
    client.post("/api/frames/1/tasks-source", json={"calendar_key": "caldav:/alice/tasks/"},
                headers=csrf_headers(client))

    client.cookies.clear()
    login(client, "bob")
    resp = client.post("/api/frames/1/tasks-source", json={"calendar_key": None}, headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    cfg = db_session.get(CalendarWidgetConfig, widget.id)
    assert cfg.tasks_user_id is None
    assert cfg.tasks_calendar_key is None


def test_tasks_source_404s_when_frame_has_no_calendar_widget(client, db_session):
    _setup_two_linked_users(client, db_session)
    resp = client.post("/api/frames/1/tasks-source", json={"calendar_key": "caldav:/some/tasks/"},
                       headers=csrf_headers(client))
    assert resp.status_code == 404


# --- calendar-select ---

def test_calendar_select_bob_cannot_add_alices_calendar(client, db_session):
    _setup_two_linked_users(client, db_session)
    alice_id = db_session.query(User).filter_by(username="alice").one().id

    client.cookies.clear()
    login(client, "bob")
    resp = client.post("/api/frames/1/calendar-select", json={
        "user_id": alice_id, "calendar_key": "ics", "included": True,
    }, headers=csrf_headers(client))
    assert resp.status_code == 403


def test_calendar_select_owner_can_add_their_own(client, db_session):
    _setup_two_linked_users(client, db_session)
    alice_id = db_session.query(User).filter_by(username="alice").one().id

    resp = client.post("/api/frames/1/calendar-select", json={
        "user_id": alice_id, "calendar_key": "ics", "included": True,
    }, headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    row = db_session.query(FrameCalendar).filter_by(frame_id=1, user_id=alice_id, calendar_key="ics").one()
    assert row.included is True


def test_calendar_select_bob_can_mute_alices_calendar(client, db_session):
    """Muting is a display-preference veto anyone linked gets, unlike
    adding -- the one-sided half of this endpoint's permission split."""
    _setup_two_linked_users(client, db_session)
    alice_id = db_session.query(User).filter_by(username="alice").one().id
    client.post("/api/frames/1/calendar-select", json={
        "user_id": alice_id, "calendar_key": "ics", "included": True,
    }, headers=csrf_headers(client))

    client.cookies.clear()
    login(client, "bob")
    resp = client.post("/api/frames/1/calendar-select", json={
        "user_id": alice_id, "calendar_key": "ics", "included": False,
    }, headers=csrf_headers(client))
    assert resp.status_code == 200, resp.text

    row = db_session.query(FrameCalendar).filter_by(frame_id=1, user_id=alice_id, calendar_key="ics").one()
    assert row.included is False


def test_calendar_select_cannot_mute_a_calendar_that_was_never_added(client, db_session):
    _setup_two_linked_users(client, db_session)
    alice_id = db_session.query(User).filter_by(username="alice").one().id

    client.cookies.clear()
    login(client, "bob")
    resp = client.post("/api/frames/1/calendar-select", json={
        "user_id": alice_id, "calendar_key": "ics", "included": False,
    }, headers=csrf_headers(client))
    assert resp.status_code == 404
