# retoor <retoor@molodetz.nl>

import re
import time
import requests
from uuid import uuid4
from datetime import datetime, timedelta, timezone
from playwright.sync_api import expect
from tests.conftest import (
    BASE_URL,
    assert_no_horizontal_overflow,
    assert_share_copies,
)
from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug
DPBOT_SIZE = 112147
SOURCE_LENGTH_LIMIT = 400000
def _make_source(length):
    unit = "def handler():\n    return 0\n"
    return (unit * (length // len(unit) + 1))[:length]
def _register_session(prefix):
    name = f"{prefix}{int(time.time() * 1000)}"
    session = requests.Session()
    session.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 session, name
def _seed_gists(count):
    owner = str(uuid4())
    get_table("users").insert(
        {
            "uid": owner,
            "username": f"pag_{owner[:8]}",
            "terms_version": "1",
            "email": f"{owner[:8]}@test.devplace",
            "password_hash": "x",
            "role": "Member",
            "is_active": True,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    base = datetime(2026, 1, 1, tzinfo=timezone.utc)
    gists = get_table("gists")
    for i in range(count):
        uid = str(uuid4())
        title = f"Pag Gist {i}"
        gists.insert(
            {
                "deleted_at": None,
                "deleted_by": None,
                "uid": uid,
                "user_uid": owner,
                "slug": make_combined_slug(title, uid),
                "title": title,
                "language": "python",
                "description": "paginated",
                "stars": 0,
                "created_at": (base - timedelta(seconds=i)).isoformat(),
            }
        )
    return owner
def _set_cm_value(page, value):
    page.evaluate(f"""() => {{
        const cm = document.querySelector(".CodeMirror")?.CodeMirror;
        if (cm) {{
            cm.setValue({repr(value)});
            cm.save();
        }}
    }}""")
def _create_gist(
    page,
    title="Test Gist",
    description="Test description",
    language="python",
    source_code="print('hello')",
):
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    page.locator("#create-gist-btn").wait_for(state="visible", timeout=10000)
    page.locator("#create-gist-btn").click()
    page.wait_for_timeout(800)
    page.fill("#gist-title", title)
    page.fill("#gist-description", description)
    page.select_option("#gist-language", language)
    page.wait_for_timeout(300)
    _set_cm_value(page, source_code)
    page.wait_for_timeout(300)
    page.locator("button.btn-primary:has-text('Create Gist')").click()
    page.wait_for_timeout(2000)
    current = page.url
    if "/gists/" in current and current != f"{BASE_URL}/gists":
        return
    page.wait_for_url("**/gists/*", timeout=15000, wait_until="domcontentloaded")
def _seed_searchable_gists(language="python"):
    token = uuid4().hex[:10]
    owner = str(uuid4())
    get_table("users").insert(
        {
            "uid": owner,
            "username": f"gsrch_{owner[:8]}",
            "terms_version": "1",
            "email": f"{owner[:8]}@test.devplace",
            "password_hash": "x",
            "role": "Member",
            "is_active": True,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    base = datetime(2026, 1, 1, tzinfo=timezone.utc)
    gists = get_table("gists")
    match_title = f"Findable gist {token}"
    other_title = f"Unrelated gist {uuid4().hex[:10]}"
    for offset, title in ((0, match_title), (1, other_title)):
        uid = str(uuid4())
        gists.insert(
            {
                "uid": uid,
                "user_uid": owner,
                "slug": make_combined_slug(title, uid),
                "title": title,
                "language": language,
                "description": f"Description for {title}",
                "source_code": "x = 1",
                "stars": 0,
                "created_at": (base - timedelta(seconds=offset)).isoformat(),
                "deleted_at": None,
                "deleted_by": None,
            }
        )
    return token, match_title, other_title


def test_gist_listing_loads(page, app_server):
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    assert page.is_visible("h1:has-text('Gists')")
    assert page.is_visible("text=Share and discover code snippets")


def test_gist_listing_empty(page, app_server):
    # filter to a user with no gists so the empty state is deterministic even when
    # other tests (sharing the session DB) have created gists
    page.goto(
        f"{BASE_URL}/gists?user_uid=no-such-user-xyz", wait_until="domcontentloaded"
    )
    assert page.is_visible("text=No gists found")


def test_gist_listing_shows_sidebar(page, app_server):
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    assert page.is_visible("text=Languages")
    assert page.is_visible("text=All")


def test_guest_cannot_create(page, app_server):
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    assert not page.is_visible("#create-gist-btn")


def test_create_gist(alice, app_server):
    page, _ = alice
    title = f"Create Test {int(time.time())}"
    _create_gist(page, title=title, source_code="print('create test')")
    assert page.is_visible(f"text={title}")
    assert page.is_visible("text=Python")
    assert page.is_visible("text=print('create test')")


def test_gist_detail_shows_all_sections(alice, app_server):
    page, _ = alice
    title = f"Detail Test {int(time.time())}"
    _create_gist(
        page,
        title=title,
        description="See all sections",
        source_code="def hello(): pass",
    )
    assert page.is_visible("text=See all sections")
    assert page.is_visible("text=def hello(): pass")
    assert page.is_visible("text=Copy")
    assert page.is_visible("text=Back to Gists")


def test_gist_detail_back_link(alice, app_server):
    page, _ = alice
    title = f"Back Link Test {int(time.time())}"
    _create_gist(page, title=title, source_code="x = 1")
    page.click("text=Back to Gists")
    page.wait_for_url(f"{BASE_URL}/gists", timeout=10000, wait_until="domcontentloaded")
    assert page.is_visible("h1:has-text('Gists')")


def test_edit_gist(alice, app_server):
    page, _ = alice
    title = f"Edit Test Original {int(time.time())}"
    _create_gist(page, title=title, source_code="x = 1")
    page.locator("button:has-text('Edit')").wait_for(state="visible", timeout=5000)
    page.click("button:has-text('Edit')")
    page.wait_for_timeout(800)
    new_title = f"Edit Test Updated {int(time.time())}"
    page.fill("#edit-gist-title", new_title)
    page.select_option("#edit-gist-language", "javascript")
    _set_cm_value(page, "const x = 1;")
    page.wait_for_timeout(300)
    page.locator("button.btn-primary:has-text('Save Changes')").click()
    page.wait_for_url("**/gists/*", timeout=15000, wait_until="domcontentloaded")
    assert page.is_visible(f"text={new_title}")
    assert page.is_visible("text=JavaScript")


def test_non_owner_cannot_edit_or_delete(bob, alice, app_server):
    bob_page, _ = bob
    alice_page, alice_user = alice
    title = f"Owner Check {int(time.time())}"
    _create_gist(alice_page, title=title, source_code="owner_only = True")
    slug = alice_page.url.split("/gists/")[-1]
    bob_page.goto(f"{BASE_URL}/gists/{slug}", wait_until="domcontentloaded")
    assert not bob_page.is_visible("button:has-text('Edit')")
    assert not bob_page.is_visible("button:has-text('Delete')")


def test_delete_gist(alice, app_server):
    page, _ = alice
    title = f"Delete Test {int(time.time())}"
    _create_gist(page, title=title, source_code="delete_me = True")
    page.locator("button:has-text('Delete')").wait_for(state="visible", timeout=5000)
    page.click("button:has-text('Delete')")
    page.locator(".dialog-overlay.visible .dialog-confirm").click()
    page.wait_for_url(f"{BASE_URL}/gists", timeout=10000, wait_until="domcontentloaded")


def test_gist_voting(alice, app_server):
    page, _ = alice
    title = f"Vote Test {int(time.time())}"
    _create_gist(page, title=title, source_code="vote_me = True")
    star_btn = page.locator("form[action*='/votes/gist/'] button").first
    original_text = star_btn.text_content()
    original_stars = int(original_text.strip("\u2606 "))
    star_btn.click()
    page.wait_for_timeout(500)
    star_btn = page.locator("form[action*='/votes/gist/'] button").first
    new_text = star_btn.text_content()
    new_stars = int(new_text.strip("\u2606 "))
    assert new_stars == original_stars + 1


def test_gist_detail_has_sourcecode_schema(alice, app_server):
    page, _ = alice
    _create_gist(page, title=f"Schema Gist {int(time.time())}", source_code="x = 1")
    scripts = page.locator('script[type="application/ld+json"]')
    text = " ".join(scripts.nth(i).text_content() for i in range(scripts.count()))
    assert "SoftwareSourceCode" in text


def test_gist_detail_share_button(alice, app_server):
    page, _ = alice
    _create_gist(page, title=f"Share Gist {int(time.time())}", source_code="x = 1")
    assert_share_copies(page, "/gists/")


def test_gist_code_copy_button(alice, app_server):
    from playwright.sync_api import expect

    page, _ = alice
    _create_gist(page, title=f"Copy Gist {int(time.time())}", source_code="copyme = 42")
    btn = page.locator("button[data-copy='gist-code-content']")
    btn.click()
    expect(btn).to_have_text("Copied!", timeout=3000)


def test_language_filter(alice, app_server):
    page, _ = alice
    title = f"Lang Filter Test {int(time.time())}"
    _create_gist(page, title=title, language="go", source_code="package main")
    page.goto(f"{BASE_URL}/gists?language=go", wait_until="domcontentloaded")
    assert page.is_visible(f"text={title}")
    page.goto(f"{BASE_URL}/gists?language=python", wait_until="domcontentloaded")
    assert not page.is_visible(f"text={title}")


def test_gists_search_bar_visible(alice):
    page, _ = alice
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    search = page.locator("input[name='search'][placeholder='Search gists...']")
    assert search.is_visible()


def test_gists_search_filters(alice):
    page, _ = alice
    token, match_title, other_title = _seed_searchable_gists()
    page.goto(f"{BASE_URL}/gists?search={token}", wait_until="domcontentloaded")
    assert page.locator(f".gist-card-title:has-text('{match_title}')").count() == 1
    assert page.locator(f".gist-card-title:has-text('{other_title}')").count() == 0


def test_gists_search_no_match(alice):
    page, _ = alice
    _seed_searchable_gists()
    page.goto(
        f"{BASE_URL}/gists?search=zzz{uuid4().hex}", wait_until="domcontentloaded"
    )
    assert page.locator(".gist-card-title").count() == 0


def test_gists_search_preserves_language(alice):
    page, _ = alice
    token, match_title, _ = _seed_searchable_gists(language="go")
    page.goto(
        f"{BASE_URL}/gists?language=go&search={token}", wait_until="domcontentloaded"
    )
    assert page.locator(f".gist-card-title:has-text('{match_title}')").count() == 1
    preserved = page.locator(
        ".sidebar-card form input[type='hidden'][name='language'][value='go']"
    )
    assert preserved.count() == 1


def test_gist_comments(alice, app_server):
    page, _ = alice
    title = f"Comment Test {int(time.time())}"
    _create_gist(page, title=title, source_code="has_comments = True")
    comment_text = f"Nice gist! {int(time.time())}"
    comment_input = page.locator("textarea[name='content']").first
    comment_input.wait_for(state="visible", timeout=5000)
    comment_input.fill(comment_text)
    page.locator("button.comment-form-submit").first.click()
    page.wait_for_url("**/gists/*", timeout=10000, wait_until="domcontentloaded")
    assert page.is_visible(f"text={comment_text}")


def test_gist_listing_shows_created_gist(alice, app_server):
    page, _ = alice
    title = f"Listing Shows {int(time.time())}"
    _create_gist(page, title=title, source_code="show_in_list = True")
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    assert page.is_visible(f"text={title}")


def test_gist_pagination_first_page(alice):
    page, _ = alice
    owner = _seed_gists(26)
    page.goto(f"{BASE_URL}/gists?user_uid={owner}", wait_until="domcontentloaded")
    assert page.locator(".gist-card").count() == 25
    assert page.is_visible(".load-more-wrap")


def test_gist_pagination_load_more(alice):
    page, _ = alice
    owner = _seed_gists(26)
    page.goto(f"{BASE_URL}/gists?user_uid={owner}", wait_until="domcontentloaded")
    page.click(".load-more-wrap a")
    page.wait_for_url(lambda url: "before=" in url, wait_until="domcontentloaded")
    assert f"user_uid={owner}" in page.url
    assert page.locator(".gist-card").count() == 1
    assert not page.is_visible(".load-more-wrap")


def test_gist_no_pagination_below_page_size(alice):
    page, _ = alice
    owner = _seed_gists(10)
    page.goto(f"{BASE_URL}/gists?user_uid={owner}", wait_until="domcontentloaded")
    assert page.locator(".gist-card").count() == 10
    assert not page.is_visible(".load-more-wrap")


def test_gist_voted_state_persists(alice):
    page, _ = alice
    _create_gist(page, title="Voted State Gist")
    star = "form[action*='/votes/gist/'] button"
    page.locator(star).first.click()
    page.wait_for_timeout(500)
    page.reload(wait_until="domcontentloaded")
    expect(page.locator(star).first).to_have_class(re.compile(r"\bvoted\b"))


def test_oversized_gist_shows_client_error(alice, app_server):
    page, _ = alice
    title = f"Client Oversize {int(time.time())}"
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    page.locator("#create-gist-btn").wait_for(state="visible", timeout=10000)
    page.locator("#create-gist-btn").click()
    page.wait_for_timeout(800)
    page.fill("#gist-title", title)
    page.wait_for_timeout(300)
    _set_cm_value(page, _make_source(SOURCE_LENGTH_LIMIT + 1))
    page.wait_for_timeout(300)
    page.locator("button.btn-primary:has-text('Create Gist')").click()
    error = page.locator(".gist-length-error")
    error.wait_for(state="visible", timeout=5000)
    assert "maximum" in error.text_content()
    assert page.url.rstrip("/") == f"{BASE_URL}/gists"
    assert get_table("gists").find_one(title=title) is None


def test_create_large_gist_via_ui_saves(alice, app_server):
    page, _ = alice
    title = f"UI Large {int(time.time())}"
    source = _make_source(DPBOT_SIZE)
    _create_gist(page, title=title, description="ui large", source_code=source)
    assert "/gists/" in page.url
    saved = get_table("gists").find_one(title=title)
    assert saved is not None
    assert len(saved["source_code"]) > DPBOT_SIZE // 2


def _seed_gist_with_comments():
    owner = str(uuid4())
    get_table("users").insert(
        {
            "uid": owner,
            "username": f"gcseed_{owner[:8]}",
            "terms_version": "1",
            "email": f"{owner[:8]}@gc.seed",
            "password_hash": "x",
            "role": "Member",
            "is_active": True,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    uid = str(uuid4())
    marker = f"gistcom-{uid[:8]}"
    get_table("gists").insert(
        {
            "deleted_at": None,
            "deleted_by": None,
            "uid": uid,
            "user_uid": owner,
            "slug": make_combined_slug(marker, uid),
            "title": marker,
            "description": "Gist with comment hierarchy.",
            "source_code": "print('x')",
            "language": "python",
            "stars": 0,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    parent = str(uuid4())
    base = datetime.now(timezone.utc)
    rows = [
        (f"{marker}-excluded", str(uuid4()), None, -2),
        (f"{marker}-flat", str(uuid4()), None, -1),
        (f"{marker}-parent", parent, None, 0),
        (f"{marker}-reply", str(uuid4()), parent, 1),
    ]
    comments = get_table("comments")
    for content, cuid, par, off in rows:
        comments.insert(
            {
                "deleted_at": None,
                "deleted_by": None,
                "uid": cuid,
                "target_type": "gist",
                "target_uid": uid,
                "user_uid": owner,
                "content": content,
                "parent_uid": par,
                "created_at": (base + timedelta(seconds=off)).isoformat(),
            }
        )
    return marker


def test_gists_list_preserves_comment_hierarchy(page, app_server):
    marker = _seed_gist_with_comments()
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    card = page.locator(".gist-card").filter(has_text=marker).first
    card.wait_for(state="visible")
    previews = card.locator(".post-card-comments .comment-text")
    expect(previews).to_have_count(3)
    expect(card.locator(".post-card-comments")).not_to_contain_text(f"{marker}-excluded")
    nested = card.locator(".post-card-comments .comment-replies .comment-text")
    expect(nested).to_contain_text(f"{marker}-reply")


def _seed_gist_with_long_code_line():
    owner = str(uuid4())
    get_table("users").insert(
        {
            "uid": owner,
            "username": f"gcode_{owner[:8]}",
            "email": f"{owner[:8]}@gcode.seed",
            "password_hash": "x",
            "role": "Member",
            "is_active": True,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    uid = str(uuid4())
    marker = f"gistcode-{uid[:8]}"
    line = "unbreakable_identifier_" * 8
    get_table("gists").insert(
        {
            "deleted_at": None,
            "deleted_by": None,
            "uid": uid,
            "user_uid": owner,
            "slug": make_combined_slug(marker, uid),
            "title": marker,
            "description": f"```python\n{line}\n```",
            "source_code": "print('x')",
            "language": "python",
            "stars": 0,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    return marker


def test_gists_long_code_line_does_not_widen_layout(page, app_server):
    marker = _seed_gist_with_long_code_line()
    page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
    card = page.locator(".gist-card").filter(has_text=marker).first
    card.wait_for(state="visible")
    assert card.locator("pre").count() == 1
    assert_no_horizontal_overflow(page, ".gists-layout")
