# retoor <retoor@molodetz.nl>

import time
import requests
from playwright.sync_api import expect
from tests.conftest import BASE_URL
from devplacepy.database import get_table
_counter_project_files = [0]
def _signup_project_files():
    _counter_project_files[0] += 1
    name = f"pf{int(time.time() * 1000)}{_counter_project_files[0]}"
    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,
    )
    key = get_table("users").find_one(username=name)["api_key"]
    return name, key
def _h_project_files(key):
    return {"X-API-KEY": key, "Accept": "application/json"}
def _create_project_project_files(key, title):
    r = requests.post(
        f"{BASE_URL}/projects/create",
        headers=_h_project_files(key),
        data={
            "title": title,
            "description": "filesystem test",
            "project_type": "software",
            "status": "In Development",
        },
    )
    assert r.status_code == 200, r.text
    return r.json()["data"]
def _write_project_files(key, slug, path, content):
    return requests.post(
        f"{BASE_URL}/projects/{slug}/files/write",
        headers=_h_project_files(key),
        data={"path": path, "content": content},
        allow_redirects=False,
    )
def _mkdir(key, slug, path):
    return requests.post(
        f"{BASE_URL}/projects/{slug}/files/mkdir",
        headers=_h_project_files(key),
        data={"path": path},
        allow_redirects=False,
    )
def _move(key, slug, from_path, to_path):
    return requests.post(
        f"{BASE_URL}/projects/{slug}/files/move",
        headers=_h_project_files(key),
        data={"from_path": from_path, "to_path": to_path},
        allow_redirects=False,
    )
def _delete(key, slug, path):
    return requests.post(
        f"{BASE_URL}/projects/{slug}/files/delete",
        headers=_h_project_files(key),
        data={"path": path},
        allow_redirects=False,
    )
def _list(slug, key=None):
    return requests.get(
        f"{BASE_URL}/projects/{slug}/files",
        headers=_h_project_files(key) if key else {"Accept": "application/json"},
    )
def _raw_project_files(slug, path, key=None):
    return requests.get(
        f"{BASE_URL}/projects/{slug}/files/raw",
        params={"path": path},
        headers=_h_project_files(key) if key else {"Accept": "application/json"},
    )
def _paths(slug, key=None):
    return sorted(f["path"] for f in _list(slug, key).json()["files"])
def _make_project_ui(page, title):
    page.goto(f"{BASE_URL}/projects", wait_until="domcontentloaded")
    page.locator("#create-project-btn").click()
    page.fill("#title", title)
    page.fill("#description", "Project for filesystem UI test")
    page.click("button:has-text('Create Project')")
    page.wait_for_url(f"{BASE_URL}/projects/*", wait_until="domcontentloaded")
    return page.url
def _open_files(page, title):
    proj_url = _make_project_ui(page, title)
    slug = proj_url.rstrip("/").split("/")[-1]
    page.goto(proj_url + "/files", wait_until="domcontentloaded")
    return slug
def _dialog_fill(page, value):
    page.locator(".dialog-overlay.visible .dialog-input").wait_for(state="visible")
    page.fill(".dialog-overlay.visible .dialog-input", value)
    page.click(".dialog-overlay.visible .dialog-confirm")
def _dialog_confirm(page):
    page.locator(".dialog-overlay.visible .dialog-confirm").wait_for(state="visible")
    page.click(".dialog-overlay.visible .dialog-confirm")
def _dialog_cancel(page):
    page.locator(".dialog-overlay.visible .dialog-cancel").wait_for(state="visible")
    page.click(".dialog-overlay.visible .dialog-cancel")
def _new_folder(page, name):
    page.click("#pf-new-folder")
    _dialog_fill(page, name)
    page.wait_for_selector(f".pf-node-row:has-text('{name.split('/')[-1]}')")
def _new_file(page, name):
    page.click("#pf-new-file")
    _dialog_fill(page, name)
def _row(page, name):
    return page.locator(f".pf-node-row:has-text('{name}')").first
def _alice_key():
    return get_table("users").find_one(username="alice_test")["api_key"]


def test_files_link_on_detail(alice):
    page, _ = alice
    _make_project_ui(page, "UI Files Link")
    link = page.locator(".project-detail-actions a:has-text('Files')")
    expect(link).to_be_visible()
    link.click()
    page.wait_for_url("**/files", wait_until="domcontentloaded")
    expect(page.locator("#pf-tree")).to_be_visible()


def test_files_page_owner_toolbar(alice):
    page, _ = alice
    _make_project_ui(page, "UI Owner Toolbar")
    page.goto(page.url + "/files", wait_until="domcontentloaded")
    for sel in ("#pf-new-file", "#pf-new-folder", "#pf-upload", "#pf-save"):
        expect(page.locator(sel)).to_be_visible()


def test_create_file_via_ui(alice):
    page, _ = alice
    _make_project_ui(page, "UI Create File")
    page.goto(page.url + "/files", wait_until="domcontentloaded")
    _new_file(page, "src/hello.py")
    expect(page.locator(".pf-node-row:has-text('hello.py')")).to_be_visible()
    expect(page.locator(".pf-node-row:has-text('src')")).to_be_visible()


def test_edit_and_save_via_ui(alice):
    page, _ = alice
    proj_url = _make_project_ui(page, "UI Edit Save")
    slug = proj_url.rstrip("/").split("/")[-1]
    page.goto(proj_url + "/files", wait_until="domcontentloaded")
    _new_file(page, "main.py")
    page.locator(".pf-node-row:has-text('main.py')").click()
    page.wait_for_function(
        "window.app && window.app.projectFiles && window.app.projectFiles.currentPath === 'main.py'"
    )
    page.wait_for_timeout(300)
    page.evaluate("window.app.projectFiles.editor.setValue('print(42)')")
    page.click("#pf-save")
    expect(page.locator("#pf-status")).to_have_text("Saved", timeout=4000)
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "main.py").json()["content"] == "print(42)"


def test_create_folder_via_ui(alice):
    page, _ = alice
    _make_project_ui(page, "UI Create Folder")
    page.goto(page.url + "/files", wait_until="domcontentloaded")
    _new_folder(page, "components")
    expect(page.locator(".pf-node-row:has-text('components')")).to_be_visible()


def test_upload_via_ui(alice):
    page, _ = alice
    _make_project_ui(page, "UI Upload")
    page.goto(page.url + "/files", wait_until="domcontentloaded")
    with page.expect_file_chooser() as fc:
        page.click("#pf-upload")
    fc.value.set_files(
        [
            {
                "name": "data.json",
                "mimeType": "application/json",
                "buffer": b'{"a":1}',
            }
        ]
    )
    expect(page.locator(".pf-node-row:has-text('data.json')")).to_be_visible()


def test_delete_via_ui(alice):
    page, _ = alice
    _make_project_ui(page, "UI Delete")
    page.goto(page.url + "/files", wait_until="domcontentloaded")
    _new_file(page, "trash.txt")
    page.locator(".pf-node-row:has-text('trash.txt')").click()
    page.click("#pf-delete")
    _dialog_confirm(page)
    expect(page.locator(".pf-node-row:has-text('trash.txt')")).to_have_count(0)


def test_non_owner_readonly_ui(alice, bob):
    alice_page, _ = alice
    proj_url = _make_project_ui(alice_page, "UI Readonly")
    alice_page.goto(proj_url + "/files", wait_until="domcontentloaded")
    _new_file(alice_page, "shared.py")
    expect(alice_page.locator(".pf-node-row:has-text('shared.py')")).to_be_visible()

    bob_page, _ = bob
    bob_page.goto(proj_url + "/files", wait_until="domcontentloaded")
    expect(bob_page.locator(".pf-node-row:has-text('shared.py')")).to_be_visible()
    expect(bob_page.locator("#pf-new-file")).to_have_count(0)
    expect(bob_page.locator("#pf-save")).to_have_count(0)


def test_add_file_into_selected_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd File")
    _new_folder(page, "src")
    page.locator(".pf-node-row:has-text('src')").click()
    _new_file(page, "main.py")
    expect(page.locator(".pf-node-row:has-text('main.py')")).to_be_visible()
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "src/main.py").status_code == 200
    assert _raw_project_files(slug, "main.py").status_code == 404


def test_add_folder_into_selected_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd Folder")
    _new_folder(page, "src")
    page.locator(".pf-node-row:has-text('src')").click()
    _new_folder(page, "components")
    expect(page.locator(".pf-node-row:has-text('components')")).to_be_visible()
    page.wait_for_timeout(300)
    assert "src/components" in _paths(slug)


def test_upload_into_selected_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd Upload")
    _new_folder(page, "assets")
    page.locator(".pf-node-row:has-text('assets')").click()
    with page.expect_file_chooser() as fc:
        page.click("#pf-upload")
    fc.value.set_files(
        [
            {
                "name": "config.json",
                "mimeType": "application/json",
                "buffer": b'{"k":1}',
            }
        ]
    )
    page.wait_for_timeout(400)
    assert _raw_project_files(slug, "assets/config.json").status_code == 200


def test_add_file_into_nested_selected_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd Nested")
    _new_folder(page, "src/api")
    page.locator(".pf-node-row:has-text('api')").click()
    _new_file(page, "routes.py")
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "src/api/routes.py").status_code == 200


def test_new_folder_then_add_file_inside_without_reselect(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd Chained")
    _new_folder(page, "lib")
    _new_file(page, "index.js")
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "lib/index.js").status_code == 200


def test_add_file_uses_parent_of_selected_file(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd Sibling")
    _new_folder(page, "src")
    page.locator(".pf-node-row:has-text('src')").click()
    _new_file(page, "a.py")
    page.locator(".pf-node-row:has-text('a.py')").click()
    _new_file(page, "b.py")
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "src/a.py").status_code == 200
    assert _raw_project_files(slug, "src/b.py").status_code == 200


def test_add_file_at_root_when_nothing_selected(alice):
    page, _ = alice
    slug = _open_files(page, "UI DirAdd Root")
    _new_file(page, "top.py")
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "top.py").status_code == 200


def test_context_menu_opens_on_right_click(alice):
    page, _ = alice
    _open_files(page, "UI CtxOpen")
    _new_file(page, "a.py")
    _row(page, "a.py").click(button="right")
    menu = page.locator(".context-menu.visible")
    expect(menu).to_be_visible()
    expect(menu).to_contain_text("Rename")
    expect(menu).to_contain_text("Delete")


def test_context_menu_closes_on_outside_click(alice):
    page, _ = alice
    _open_files(page, "UI CtxClose")
    _new_file(page, "a.py")
    _row(page, "a.py").click(button="right")
    expect(page.locator(".context-menu.visible")).to_be_visible()
    page.locator("#pf-current").click()
    expect(page.locator(".context-menu.visible")).to_have_count(0)


def test_context_menu_rename(alice):
    page, _ = alice
    slug = _open_files(page, "UI CtxRename")
    _new_file(page, "old.py")
    _row(page, "old.py").click(button="right")
    page.locator(".context-menu.visible .context-menu-item:has-text('Rename')").click()
    _dialog_fill(page, "renamed.py")
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "renamed.py").status_code == 200
    assert _raw_project_files(slug, "old.py").status_code == 404


def test_context_menu_delete_confirm(alice):
    page, _ = alice
    slug = _open_files(page, "UI CtxDelete")
    _new_file(page, "gone.py")
    _row(page, "gone.py").click(button="right")
    page.locator(".context-menu.visible .context-menu-item:has-text('Delete')").click()
    _dialog_confirm(page)
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "gone.py").status_code == 404


def test_context_menu_delete_cancel_keeps_file(alice):
    page, _ = alice
    slug = _open_files(page, "UI CtxDeleteCancel")
    _new_file(page, "stay.py")
    _row(page, "stay.py").click(button="right")
    page.locator(".context-menu.visible .context-menu-item:has-text('Delete')").click()
    _dialog_cancel(page)
    page.wait_for_timeout(200)
    assert _raw_project_files(slug, "stay.py").status_code == 200


def test_context_menu_new_file_in_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI CtxNewInDir")
    _new_folder(page, "src")
    _row(page, "src").click(button="right")
    page.locator(
        ".context-menu.visible .context-menu-item:has-text('New file here')"
    ).click()
    _dialog_fill(page, "inside.py")
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "src/inside.py").status_code == 200


def test_multi_select_ctrl_and_delete(alice):
    page, _ = alice
    slug = _open_files(page, "UI MultiDelete")
    _new_file(page, "a.py")
    _new_file(page, "b.py")
    _new_file(page, "c.py")
    _row(page, "a.py").click(modifiers=["Control"])
    _row(page, "c.py").click(modifiers=["Control"])
    expect(page.locator(".pf-node-row.pf-selected")).to_have_count(2)
    _row(page, "c.py").click(button="right")
    page.locator(
        ".context-menu.visible .context-menu-item:has-text('Delete 2 items')"
    ).click()
    _dialog_confirm(page)
    page.wait_for_timeout(400)
    assert _raw_project_files(slug, "a.py").status_code == 404
    assert _raw_project_files(slug, "c.py").status_code == 404
    assert _raw_project_files(slug, "b.py").status_code == 200


def test_shift_select_range(alice):
    page, _ = alice
    _open_files(page, "UI ShiftRange")
    _new_file(page, "a.py")
    _new_file(page, "b.py")
    _new_file(page, "c.py")
    _row(page, "a.py").click()
    _row(page, "c.py").click(modifiers=["Shift"])
    expect(page.locator(".pf-node-row.pf-selected")).to_have_count(3)


def test_drag_file_into_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI DragIntoDir")
    _new_folder(page, "src")
    _new_file(page, "move.py")
    _row(page, "move.py").drag_to(_row(page, "src"))
    page.wait_for_timeout(400)
    assert _raw_project_files(slug, "src/move.py").status_code == 200
    assert _raw_project_files(slug, "move.py").status_code == 404


def test_drag_file_to_root(alice):
    page, _ = alice
    slug = _open_files(page, "UI DragToRoot")
    _new_folder(page, "src")
    _row(page, "src").click()
    _new_file(page, "deep.py")
    page.wait_for_timeout(200)
    assert _raw_project_files(slug, "src/deep.py").status_code == 200
    _row(page, "deep.py").drag_to(page.locator(".pf-root-row"))
    page.wait_for_timeout(400)
    assert _raw_project_files(slug, "deep.py").status_code == 200
    assert _raw_project_files(slug, "src/deep.py").status_code == 404


def test_drag_multi_selection_into_directory(alice):
    page, _ = alice
    slug = _open_files(page, "UI DragMulti")
    _new_folder(page, "dest")
    _new_file(page, "x.py")
    _new_file(page, "y.py")
    _row(page, "x.py").click(modifiers=["Control"])
    _row(page, "y.py").click(modifiers=["Control"])
    _row(page, "x.py").drag_to(_row(page, "dest"))
    page.wait_for_timeout(500)
    assert _raw_project_files(slug, "dest/x.py").status_code == 200
    assert _raw_project_files(slug, "dest/y.py").status_code == 200


def test_toolbar_delete_uses_custom_dialog(alice):
    page, _ = alice
    slug = _open_files(page, "UI ToolbarDelete")
    _new_file(page, "t.py")
    _row(page, "t.py").click()
    page.click("#pf-delete")
    expect(page.locator(".dialog-overlay.visible")).to_be_visible()
    _dialog_confirm(page)
    page.wait_for_timeout(300)
    assert _raw_project_files(slug, "t.py").status_code == 404


def test_empty_project_shows_hint(alice):
    page, _ = alice
    _open_files(page, "EmptyHint")
    expect(page.locator("#pf-empty")).to_be_visible()
    expect(page.locator("#pf-editor-wrap")).to_be_hidden()
    expect(page.locator(".pf-empty-msg")).to_contain_text("Create a file")
