"""Route-level tests for the bookmarks feature: CRUD, upsert-on-duplicate,
ownership, auth, bulk import (dedupe/counts), and the per-user cap. Uses a
throwaway accounts DB (conftest points THERMOGRAPH_ACCOUNTS_DB at a temp file),
same as test_api_accounts.py."""
import pytest
from fastapi.testclient import TestClient

from web import app as appmod
from accounts import db

V2 = "/thermograph/api/v2"
PW = "supersecret123"


@pytest.fixture(scope="module", autouse=True)
def _tables():
    # TestClient(app) (no context manager) skips the lifespan, so create the
    # account tables directly against the temp DB.
    db.Base.metadata.create_all(db.sync_engine)


def _client():
    return TestClient(appmod.app)


def _login(client, email):
    r = client.post(f"{V2}/auth/register", json={"email": email, "password": PW})
    assert r.status_code in (201, 400)          # 400 only if the email already exists
    r = client.post(f"{V2}/auth/login", data={"username": email, "password": PW})
    assert r.status_code == 204                  # sets the session cookie on the client


def test_bookmarks_require_auth():
    c = _client()
    assert c.get(f"{V2}/bookmarks").status_code == 401
    assert c.post(f"{V2}/bookmarks", json={"lat": 1, "lon": 1, "label": "x"}).status_code == 401
    assert c.post(f"{V2}/bookmarks/import", json={"items": []}).status_code == 401


def test_bookmark_crud():
    c = _client()
    _login(c, "bm-crud@example.com")

    r = c.post(f"{V2}/bookmarks", json={"lat": 47.6, "lon": -122.3, "label": "Seattle"})
    assert r.status_code == 201
    bm = r.json()
    assert bm["cell_id"] and bm["label"] == "Seattle"
    assert bm["lat"] == 47.6 and bm["lon"] == -122.3

    listed = c.get(f"{V2}/bookmarks").json()["bookmarks"]
    assert len(listed) == 1 and listed[0]["id"] == bm["id"]

    patched = c.patch(f"{V2}/bookmarks/{bm['id']}", json={"label": "Home"})
    assert patched.status_code == 200
    assert patched.json()["label"] == "Home"

    assert c.delete(f"{V2}/bookmarks/{bm['id']}").status_code == 204
    assert c.get(f"{V2}/bookmarks").json()["bookmarks"] == []


def test_bookmark_upsert_on_duplicate():
    c = _client()
    _login(c, "bm-upsert@example.com")
    body = {"lat": 33.4, "lon": -112.0, "label": "Phoenix"}

    first = c.post(f"{V2}/bookmarks", json=body)
    assert first.status_code == 201
    bm_id = first.json()["id"]

    # Re-bookmarking the same location (same cell) upserts the label — 200, not 409.
    second = c.post(f"{V2}/bookmarks", json={**body, "label": "Phoenix Home"})
    assert second.status_code == 200
    assert second.json()["id"] == bm_id
    assert second.json()["label"] == "Phoenix Home"

    assert len(c.get(f"{V2}/bookmarks").json()["bookmarks"]) == 1


def test_bookmark_validation():
    c = _client()
    _login(c, "bm-valid@example.com")
    assert c.post(f"{V2}/bookmarks", json={"lat": 1, "lon": 1, "label": "   "}).status_code == 422
    assert c.post(f"{V2}/bookmarks", json={"lat": 1, "lon": 1, "label": "x" * 81}).status_code == 422
    assert c.post(f"{V2}/bookmarks", json={"lat": 91, "lon": 1, "label": "x"}).status_code == 422
    assert c.post(f"{V2}/bookmarks", json={"lat": 1, "lon": 181, "label": "x"}).status_code == 422
    # a label that's only over-length due to surrounding whitespace is fine once trimmed
    ok = c.post(f"{V2}/bookmarks", json={"lat": 2, "lon": 2, "label": "  Trimmed  "})
    assert ok.status_code == 201 and ok.json()["label"] == "Trimmed"


def test_ownership_is_404():
    a, b = _client(), _client()
    _login(a, "bm-owner-a@example.com")
    _login(b, "bm-owner-b@example.com")
    bm_id = a.post(f"{V2}/bookmarks", json={"lat": 40.7, "lon": -74.0, "label": "NYC"}).json()["id"]

    # B cannot see, patch, or delete A's bookmark — 404, not 403 (no existence leak)
    assert b.patch(f"{V2}/bookmarks/{bm_id}", json={"label": "Nope"}).status_code == 404
    assert b.delete(f"{V2}/bookmarks/{bm_id}").status_code == 404
    assert b.get(f"{V2}/bookmarks").json()["bookmarks"] == []


def test_bookmark_import_dedupe_and_counts():
    c = _client()
    _login(c, "bm-import@example.com")
    # Pre-existing bookmark at the Seattle cell, with a label import must NOT overwrite.
    existing = c.post(
        f"{V2}/bookmarks", json={"lat": 47.6, "lon": -122.3, "label": "Original"}
    ).json()
    assert existing["label"] == "Original"

    r = c.post(f"{V2}/bookmarks/import", json={"items": [
        {"lat": 47.6, "lon": -122.3, "label": "Should Not Overwrite"},  # dup of existing cell
        {"lat": 34.0, "lon": -118.2, "label": "LA"},                    # new
        {"lat": 34.0, "lon": -118.2, "label": "LA Dup"},                # dup within payload
        {"lat": 51.5, "lon": -0.1, "label": "London"},                  # new
    ]})
    assert r.status_code == 200
    body = r.json()
    assert body["imported"] == 2
    assert body["skipped"] == 2
    assert len(body["bookmarks"]) == 3

    by_label = {b["label"] for b in body["bookmarks"]}
    assert by_label == {"Original", "LA", "London"}   # existing label untouched


def test_bookmark_cap():
    c = _client()
    _login(c, "bm-cap@example.com")
    for i in range(200):
        r = c.post(f"{V2}/bookmarks", json={"lat": i * 0.1, "lon": 0.0, "label": f"spot-{i}"})
        assert r.status_code == 201, r.text

    over = c.post(f"{V2}/bookmarks", json={"lat": 89.0, "lon": 179.0, "label": "one too many"})
    assert over.status_code == 409

    assert len(c.get(f"{V2}/bookmarks").json()["bookmarks"]) == 200


def test_bookmark_import_stops_at_cap():
    c = _client()
    _login(c, "bm-import-cap@example.com")
    for i in range(198):
        r = c.post(f"{V2}/bookmarks", json={"lat": i * 0.1, "lon": 10.0, "label": f"spot-{i}"})
        assert r.status_code == 201, r.text

    r = c.post(f"{V2}/bookmarks/import", json={"items": [
        {"lat": 60.0, "lon": 60.0, "label": "a"},
        {"lat": 61.0, "lon": 61.0, "label": "b"},
        {"lat": 62.0, "lon": 62.0, "label": "c"},
        {"lat": 63.0, "lon": 63.0, "label": "d"},
        {"lat": 64.0, "lon": 64.0, "label": "e"},
    ]})
    assert r.status_code == 200
    body = r.json()
    # Only 2 slots remained before the 200 cap; the rest count as skipped.
    assert body["imported"] == 2
    assert body["skipped"] == 3
    assert len(body["bookmarks"]) == 200
