# retoor <retoor@molodetz.nl>

import time
from datetime import datetime, timezone, timedelta
import pytest
import requests
from tests.conftest import BASE_URL
from devplacepy.database import get_table, refresh_snapshot, set_setting
from devplacepy.utils import generate_uid


JSON = {"Accept": "application/json"}
_counter = [0]


@pytest.fixture(scope="module", autouse=True)
def _devlog_test_settings(app_server):
    for key, value in {
        "rate_limit_per_minute": "1000000",
        "rate_limit_window_seconds": "60",
        "registration_open": "1",
        "maintenance_mode": "0",
    }.items():
        set_setting(key, value)
    yield


def _unique(prefix="dl"):
    _counter[0] += 1
    return f"{prefix}{int(time.time() * 1000)}{_counter[0]}"


def _member():
    name = _unique("dlmem")
    s = requests.Session()
    s.post(
        f"{BASE_URL}/auth/signup",
        data={
            "username": name,
            "email": f"{name}@t.dev",
            "password": "secret123",
            "confirm_password": "secret123",
            "birth_date": "1990-01-01",
            "accept_terms": "1",
        },
        allow_redirects=True,
    )
    return s, name


def _db_user(name):
    refresh_snapshot()
    return get_table("users").find_one(username=name)


def _create_project(session, title=None):
    title = title or _unique("dlproj")
    r = session.post(
        f"{BASE_URL}/projects/create",
        headers=JSON,
        data={
            "title": title,
            "description": "Devlog test project",
            "project_type": "software",
            "status": "In Development",
            "platforms": "",
        },
    )
    assert r.status_code == 200, r.text[:300]
    return r.json()["data"]


def _create_post(session, content, project_uid, title=None):
    title = title or _unique("dlpost")
    r = session.post(
        f"{BASE_URL}/posts/create",
        headers=JSON,
        data={
            "title": title,
            "content": content,
            "topic": "devlog",
            "project_uid": project_uid,
        },
    )
    assert r.status_code == 200, r.text[:300]
    return r.json()["data"]


def _create_post_direct(project_uid, user_uid, order, marker=None):
    """Insert a post directly into DB with precise created_at ordering."""
    uid = generate_uid()
    marker = marker or f"dlpost-{uid[:8]}"
    get_table("posts").insert(
        {
            "deleted_at": None,
            "deleted_by": None,
            "uid": uid,
            "user_uid": user_uid,
            "slug": f"{uid[:8]}-devlog-post",
            "title": marker,
            "content": f"Devlog post content {order}",
            "topic": "devlog",
            "project_uid": project_uid,
            "image": None,
            "stars": 0,
            "created_at": (datetime(2026, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=order)).isoformat(),
        }
    )
    refresh_snapshot()
    return uid, marker


def test_devlog_empty_state_when_no_posts(app_server):
    """Project with no linked posts returns empty devlog_posts list."""
    session, _ = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    assert body["devlog_posts"] == []
    assert body["devlog_next_cursor"] is None


def test_devlog_shows_linked_post(app_server):
    """A post linked via project_uid appears in the project's devlog."""
    session, name = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]

    marker = _unique("dllink")
    _create_post(session, marker, project["uid"], title=marker)

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    assert len(body["devlog_posts"]) == 1
    assert body["devlog_next_cursor"] is None
    post_item = body["devlog_posts"][0]
    assert post_item["post"]["title"] == marker
    assert post_item["author"]["username"] == name


def test_devlog_reverse_chronological_order(app_server):
    """Multiple linked posts appear newest-first."""
    session, name = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]
    user = _db_user(name)
    project_uid = project["uid"]

    markers = []
    for i in range(3):
        marker = f"dlorder-{i}-{generate_uid()[:8]}"
        markers.append(marker)
        _create_post_direct(project_uid, user["uid"], i, marker=marker)

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    titles = [item["post"]["title"] for item in body["devlog_posts"]]
    assert titles == list(reversed(markers)), (
        f"Expected newest-first order: {list(reversed(markers))}, got: {titles}"
    )


def test_devlog_pagination(app_server):
    """More than PAGE_SIZE posts produce next_cursor."""
    from devplacepy.database.pagination import PAGE_SIZE

    session, name = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]
    user = _db_user(name)
    project_uid = project["uid"]

    count = PAGE_SIZE + 1
    for i in range(count):
        _create_post_direct(project_uid, user["uid"], i, marker=f"dlpag-{i}")

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    assert len(body["devlog_posts"]) == PAGE_SIZE, (
        f"Expected {PAGE_SIZE} posts on first page, got {len(body['devlog_posts'])}"
    )
    assert body["devlog_next_cursor"] is not None, (
        "Expected next_cursor when more than PAGE_SIZE posts exist"
    )

    before = body["devlog_next_cursor"]
    r2 = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON, params={"before": before})
    assert r2.status_code == 200, r2.text[:300]
    body2 = r2.json()
    assert len(body2["devlog_posts"]) == 1, (
        f"Expected 1 post on second page, got {len(body2['devlog_posts'])}"
    )
    assert body2["devlog_next_cursor"] is None, (
        "Expected no next_cursor on last page"
    )
    r_html = session.get(f"{BASE_URL}/projects/{slug}")
    assert r_html.status_code == 200
    assert 'class="load-more-wrap"' in r_html.text, (
        "Expected Load More button in HTML for paginated devlog"
    )


def test_devlog_excludes_unlinked_posts(app_server):
    """Posts without project_uid do not appear in any project's devlog."""
    session, _ = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]

    unlinked = _unique("dlnolink")
    session.post(
        f"{BASE_URL}/posts/create",
        headers=JSON,
        data={
            "title": unlinked,
            "content": "This post has no project",
            "topic": "devlog",
        },
    )

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    titles = [item["post"]["title"] for item in body["devlog_posts"]]
    assert unlinked not in titles, (
        "Post without project_uid must not appear in devlog"
    )


def test_devlog_enriches_author_and_metadata(app_server):
    """Devlog posts include author data, comment count, and vote info."""
    session, name = _member()
    user = _db_user(name)
    project = _create_project(session)
    slug = project["slug"] or project["uid"]

    marker = _unique("dlenrich")
    post_data = _create_post(session, marker, project["uid"], title=marker)

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    assert len(body["devlog_posts"]) == 1
    item = body["devlog_posts"][0]

    assert item["author"]["username"] == name
    assert item["author"]["uid"] == user["uid"]
    assert isinstance(item["my_vote"], int)
    assert isinstance(item["comment_count"], int)
    assert item["comment_count"] == 0
    assert item["post"]["uid"] == post_data["uid"]
    assert item["post"]["slug"] == post_data["slug"]
    assert item["post"]["title"] == marker
    assert item["time_ago"] is not None


def test_devlog_works_for_guest_visitor(app_server):
    """Unauthenticated visitors can see the devlog section."""
    session, _ = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]

    r = requests.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    assert "devlog_posts" in body
    assert body["devlog_posts"] == []


def test_devlog_count_and_comment_count_in_json(app_server):
    """devlog_count and comment_count ride the detail JSON."""
    session, name = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]
    user = _db_user(name)

    for i in range(3):
        _create_post_direct(project["uid"], user["uid"], i)

    r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON)
    assert r.status_code == 200, r.text[:300]
    body = r.json()
    assert body["devlog_count"] == 3
    assert body["comment_count"] == 0


def _upload_image(session, name="shot.png", color=(30, 60, 120)):
    import io
    from PIL import Image

    buf = io.BytesIO()
    Image.new("RGB", (10, 10), color).save(buf, "PNG")
    r = session.post(
        f"{BASE_URL}/uploads/upload",
        files={"file": (name, buf.getvalue(), "image/png")},
    )
    assert r.status_code == 201, r.text[:300]
    return r.json()["uid"]


def test_cover_and_logo_attachments_render_in_hero(app_server):
    """cover/logo attachment uids resolve to the hero banner and logo tile."""
    session, _ = _member()
    cover_uid = _upload_image(session, "cover.png", (10, 20, 90))
    logo_uid = _upload_image(session, "logo.png", (90, 20, 10))

    r = session.post(
        f"{BASE_URL}/projects/create",
        headers=JSON,
        data={
            "title": _unique("dlhero"),
            "description": "Hero art test project",
            "project_type": "game",
            "status": "In Development",
            "platforms": "PC",
            "cover_attachment_uid": cover_uid,
            "logo_attachment_uid": logo_uid,
        },
    )
    assert r.status_code == 200, r.text[:300]
    slug = r.json()["data"]["slug"]

    refresh_snapshot()
    row = get_table("projects").find_one(slug=slug)
    assert row["cover_attachment_uid"] == cover_uid
    assert row["logo_attachment_uid"] == logo_uid

    html = session.get(f"{BASE_URL}/projects/{slug}").text
    assert 'class="project-cover-img"' in html
    assert 'class="project-logo"' in html
    assert "project-screenshot-grid" not in html, (
        "Cover and logo must not repeat in the Screenshots gallery"
    )


def test_hero_attachment_uid_rejects_foreign_and_missing(app_server):
    """A foreign or unknown attachment uid is ignored rather than linked."""
    owner, _ = _member()
    other, _ = _member()
    foreign_uid = _upload_image(other, "foreign.png")

    r = owner.post(
        f"{BASE_URL}/projects/create",
        headers=JSON,
        data={
            "title": _unique("dlreject"),
            "description": "Hero guard test project",
            "project_type": "software",
            "status": "In Development",
            "platforms": "",
            "cover_attachment_uid": foreign_uid,
            "logo_attachment_uid": "does-not-exist",
        },
    )
    assert r.status_code == 200, r.text[:300]
    slug = r.json()["data"]["slug"]
    refresh_snapshot()
    row = get_table("projects").find_one(slug=slug)
    assert row["cover_attachment_uid"] is None
    assert row["logo_attachment_uid"] is None


def test_owner_adds_screenshots_from_the_page(app_server):
    """POST /projects/{slug}/screenshots links uploads into the gallery; non-owners are refused."""
    session, _ = _member()
    project = _create_project(session)
    slug = project["slug"] or project["uid"]

    assert "project-screenshot-grid" not in session.get(f"{BASE_URL}/projects/{slug}").text

    uid = _upload_image(session, "gallery.png", (5, 120, 60))
    r = session.post(
        f"{BASE_URL}/projects/{slug}/screenshots",
        headers=JSON,
        data={"attachment_uids": uid},
    )
    assert r.status_code == 200, r.text[:300]
    assert r.json()["data"]["linked"] == 1

    html = session.get(f"{BASE_URL}/projects/{slug}").text
    assert "project-screenshot-grid" in html, "Expected the gallery after linking"

    intruder, _ = _member()
    r = intruder.post(
        f"{BASE_URL}/projects/{slug}/screenshots",
        headers=JSON,
        data={"attachment_uids": uid},
    )
    assert r.status_code == 403, r.text[:300]
