"""
Step 3b — Exercise JSON Export/Import Tests
- test_export_button_visible: Export button is visible on exercise list
- test_export_single_button_visible: Export button is visible on exercise detail
- test_export_import_roundtrip: Export exercises then import them back (no collision)
- test_import_invalid_json: Import rejects non-JSON file
- test_import_wrong_app: Import rejects file from different app
- test_same_user_collision_skip: Same-user import with skip action
- test_same_user_collision_replace: Same-user import with replace action
- test_same_user_collision_batch_replace_all: Batch replace all collisions
- test_same_user_collision_batch_skip_all: Batch skip all collisions
- test_different_user_suffix_prompt: Different-user import shows suffix prompt
- test_different_user_import_with_suffix: Different-user import adds suffix to titles
- test_different_user_update_same_id: Same external user same ID treated as update
- test_settings_export_filter_dialog: Settings export opens filter dialog
- test_settings_import_button: Settings import button works
- test_suffix_management_visible: Suffix section visible after import from different user
- test_suffix_rename_cascade: Renaming suffix updates titles across exercises
- test_suffix_rename_collision_rolls_back: Colliding rename fails atomically, nothing renamed
- test_suffix_delete: Suffix can be deleted from settings
- test_import_as_mine_button_visible: Import-as-mine buttons visible on exercises and settings
- test_import_as_mine_no_suffix_no_collision: Foreign exercises imported as own without suffix
- test_import_as_mine_collision_replace: Import-as-mine with collision uses replace action
- test_import_as_mine_collision_skip: Import-as-mine with collision uses skip action
- test_import_as_mine_from_settings: Import-as-mine button works from settings page
- test_import_as_mine_dialog_title: Import-as-mine shows correct dialog title
"""
import json
from playwright.sync_api import Page, expect


def _wait_for_db(page: Page, app_url: str):
    """Navigate and wait for the database to be ready."""
    page.goto(app_url)
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )


def _go_to_exercises(page: Page, app_url: str):
    """Navigate to the exercises list and wait for it to load."""
    _wait_for_db(page, app_url)
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)


def _go_to_settings(page: Page, app_url: str):
    """Navigate to the settings page."""
    _wait_for_db(page, app_url)
    page.click('a[href="#/settings"]')
    page.wait_for_timeout(500)


def _clean_all(page: Page):
    """Remove all exercises and suffixes via the repositories."""
    page.evaluate(
        """async () => {
            const exercises = await window.__repos.exercises.getAll();
            for (const ex of exercises) {
                await window.__repos.exercises.delete(ex.id);
            }
            const suffixes = await window.__repos.suffixes.getAll();
            for (const s of suffixes) {
                await window.__repos.suffixes.delete(s.user_uuid);
            }
        }"""
    )


def _create_exercise_directly(page: Page, title, created_by=None, exercise_id=None):
    """Create an exercise directly via repository for test setup."""
    return page.evaluate(
        """async ([title, createdBy, exerciseId]) => {
            const uuid = await window.__repos.settings.get('user_uuid');
            const cb = createdBy || uuid;
            if (exerciseId) {
                await window.__repos.exercises.createWithId(exerciseId, {
                    title: title,
                    description: 'Test desc',
                    asymmetric: false,
                    alternate: false,
                    image_urls: [],
                    video_urls: [],
                    default_reps: 10,
                    default_weight: 20,
                    created_by: cb,
                });
                return exerciseId;
            } else {
                const id = await window.__repos.exercises.create({
                    title: title,
                    description: 'Test desc',
                    asymmetric: false,
                    alternate: false,
                    image_urls: [],
                    video_urls: [],
                    default_reps: 10,
                    default_weight: 20,
                    created_by: cb,
                });
                return id;
            }
        }""",
        [title, created_by, exercise_id],
    )


def _build_export_json(exercises, exporter_name="Test User", exporter_uuid=None):
    """Build a valid export JSON envelope for testing."""
    envelope = {
        "metadata": {
            "appName": "trainus",
            "appVersion": "1.0.0",
            "schemaVersion": 1,
            "exportDate": "2026-03-30T12:00:00.000Z",
            "exportedBy": {
                "name": exporter_name,
                "uuid": exporter_uuid or "test-uuid-1234",
            },
        },
        "data": {"exercises": exercises},
    }
    return json.dumps(envelope)


def _get_local_uuid(page: Page):
    """Get the local user UUID."""
    return page.evaluate(
        "async () => await window.__repos.settings.get('user_uuid')"
    )


def _trigger_import_with_json(page: Page, json_str: str):
    """Trigger the import flow by injecting a file into the file picker.

    This uses a file chooser event listener to intercept the file picker
    and inject the JSON content.
    """
    with page.expect_file_chooser() as fc_info:
        page.click('[data-testid="exercise-import-btn"]')
    file_chooser = fc_info.value
    file_chooser.set_files(
        [
            {
                "name": "test-import.json",
                "mimeType": "application/json",
                "buffer": json_str.encode("utf-8"),
            }
        ]
    )


def _trigger_settings_import_with_json(page: Page, json_str: str):
    """Trigger import from settings page."""
    with page.expect_file_chooser() as fc_info:
        page.click('[data-testid="settings-import"]')
    file_chooser = fc_info.value
    file_chooser.set_files(
        [
            {
                "name": "test-import.json",
                "mimeType": "application/json",
                "buffer": json_str.encode("utf-8"),
            }
        ]
    )


def _trigger_import_as_mine_with_json(page: Page, json_str: str):
    """Trigger the 'import as mine' flow from the exercises action bar."""
    with page.expect_file_chooser() as fc_info:
        page.click('[data-testid="exercise-import-as-mine-btn"]')
    file_chooser = fc_info.value
    file_chooser.set_files(
        [
            {
                "name": "test-import.json",
                "mimeType": "application/json",
                "buffer": json_str.encode("utf-8"),
            }
        ]
    )


def _trigger_settings_import_as_mine_with_json(page: Page, json_str: str):
    """Trigger 'import as mine' from settings page."""
    with page.expect_file_chooser() as fc_info:
        page.click('[data-testid="settings-import-as-mine"]')
    file_chooser = fc_info.value
    file_chooser.set_files(
        [
            {
                "name": "test-import.json",
                "mimeType": "application/json",
                "buffer": json_str.encode("utf-8"),
            }
        ]
    )


# ============================================================
# Tests
# ============================================================


def test_export_button_visible(page: Page, app_url: str):
    """Export button is visible on the exercise list action bar."""
    _go_to_exercises(page, app_url)
    _clean_all(page)
    expect(page.locator('[data-testid="exercise-export-btn"]')).to_be_visible()
    expect(page.locator('[data-testid="exercise-import-btn"]')).to_be_visible()


def test_export_single_button_visible(page: Page, app_url: str):
    """Export button is visible on exercise detail view."""
    _go_to_exercises(page, app_url)
    _clean_all(page)
    ex_id = _create_exercise_directly(page, "Export Test Exercise")
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)
    page.click(f'[data-testid="exercise-card-{ex_id}"]')
    page.wait_for_timeout(500)
    expect(
        page.locator('[data-testid="exercise-export-single-btn"]')
    ).to_be_visible()
    # Cleanup
    _go_to_exercises(page, app_url)
    _clean_all(page)


def test_export_import_roundtrip(page: Page, app_url: str):
    """Export exercises to JSON, clear DB, import them back, verify they exist."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    # Create exercises
    _create_exercise_directly(page, "Roundtrip Exercise A")
    _create_exercise_directly(page, "Roundtrip Exercise B")

    # Get the export data by building it from the repo directly
    local_uuid = _get_local_uuid(page)
    exercises = page.evaluate(
        "async () => await window.__repos.exercises.getAll()"
    )

    # Clean exercises
    _clean_all(page)

    # Build import JSON with the local user UUID (same user)
    json_str = _build_export_json(
        exercises,
        exporter_name="Local User",
        exporter_uuid=local_uuid,
    )

    # Reload page so it picks up the clean state
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    # Trigger import
    _trigger_import_with_json(page, json_str)

    # Should see preview step (no collisions)
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-preview-step"]')).to_be_visible()

    # Click import
    page.click('[data-testid="import-apply"]')

    # Should see report
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-imported"]')).to_be_visible()

    # Close dialog
    page.click('[data-testid="import-done"]')

    # Verify exercises exist
    page.wait_for_timeout(500)
    expect(page.locator('[data-testid="exercise-card-title"]')).to_have_count(2)

    # Cleanup
    _clean_all(page)


def test_import_invalid_json(page: Page, app_url: str):
    """Import rejects a file with invalid JSON content."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    # Trigger import with invalid JSON
    page.on("dialog", lambda d: d.accept())

    with page.expect_file_chooser() as fc_info:
        page.click('[data-testid="exercise-import-btn"]')
    file_chooser = fc_info.value
    file_chooser.set_files(
        [
            {
                "name": "bad.json",
                "mimeType": "application/json",
                "buffer": b"this is not json",
            }
        ]
    )

    # An alert dialog should appear (handled by dialog listener)
    page.wait_for_timeout(1000)

    # The import dialog should NOT be visible
    expect(page.locator('[data-testid="modal-overlay"]')).not_to_be_visible()

    # Cleanup
    _clean_all(page)


def test_import_wrong_app(page: Page, app_url: str):
    """Import rejects a file from a different application."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    bad_envelope = json.dumps(
        {
            "metadata": {
                "appName": "other-app",
                "appVersion": "1.0.0",
                "schemaVersion": 1,
                "exportDate": "2026-03-30T12:00:00.000Z",
                "exportedBy": {"name": "Test", "uuid": "abc"},
            },
            "data": {"exercises": []},
        }
    )

    page.on("dialog", lambda d: d.accept())

    with page.expect_file_chooser() as fc_info:
        page.click('[data-testid="exercise-import-btn"]')
    file_chooser = fc_info.value
    file_chooser.set_files(
        [
            {
                "name": "wrong.json",
                "mimeType": "application/json",
                "buffer": bad_envelope.encode("utf-8"),
            }
        ]
    )

    page.wait_for_timeout(1000)
    expect(page.locator('[data-testid="modal-overlay"]')).not_to_be_visible()

    _clean_all(page)


def test_same_user_collision_skip(page: Page, app_url: str):
    """Same-user import with 'skip' action keeps the existing exercise."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    ex_id = _create_exercise_directly(page, "Collision Exercise")

    # Build import with same ID but different description
    exercises = [
        {
            "id": ex_id,
            "title": "Collision Exercise",
            "description": "Imported version",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 99,
            "default_weight": 99,
            "created_by": local_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Local User", local_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_with_json(page, json_str)

    # Should see collisions step
    page.wait_for_selector(
        '[data-testid="import-collisions-step"]', timeout=5000
    )

    # Default is skip, so just click apply
    page.click('[data-testid="import-apply"]')

    # Should see report with skipped
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-skipped"]')).to_be_visible()

    page.click('[data-testid="import-done"]')

    # Verify original exercise is unchanged (10 reps, not 99)
    exercise_data = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{ex_id}')"""
    )
    assert exercise_data["default_reps"] == 10

    _clean_all(page)


def test_same_user_collision_replace(page: Page, app_url: str):
    """Same-user import with 'replace' action overwrites the existing exercise."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    ex_id = _create_exercise_directly(page, "Replace Exercise")

    exercises = [
        {
            "id": ex_id,
            "title": "Replace Exercise",
            "description": "Replaced version",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 99,
            "default_weight": 99,
            "created_by": local_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Local User", local_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_with_json(page, json_str)

    page.wait_for_selector(
        '[data-testid="import-collisions-step"]', timeout=5000
    )

    # Click replace on the first collision (exercise-specific testid)
    page.click('[data-testid="collision-replace-exercise-0"]')

    page.click('[data-testid="import-apply"]')

    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-exercises-replaced"]')).to_be_visible()

    page.click('[data-testid="import-done"]')

    # Verify exercise was replaced (99 reps now)
    exercise_data = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{ex_id}')"""
    )
    assert exercise_data["default_reps"] == 99

    _clean_all(page)


def test_same_user_collision_batch_replace_all(page: Page, app_url: str):
    """Batch 'Replace All' replaces all collisions at once."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    id_a = _create_exercise_directly(page, "Batch A")
    id_b = _create_exercise_directly(page, "Batch B")

    exercises = [
        {
            "id": id_a,
            "title": "Batch A",
            "description": "Replaced A",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 77,
            "default_weight": 77,
            "created_by": local_uuid,
        },
        {
            "id": id_b,
            "title": "Batch B",
            "description": "Replaced B",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 88,
            "default_weight": 88,
            "created_by": local_uuid,
        },
    ]
    json_str = _build_export_json(exercises, "Local User", local_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_with_json(page, json_str)

    page.wait_for_selector(
        '[data-testid="import-collisions-step"]', timeout=5000
    )

    # Click Replace All (exercise-specific testid)
    page.click('[data-testid="exercise-replace-all"]')
    page.click('[data-testid="import-apply"]')

    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-exercises-replaced"]')).to_contain_text("2")

    page.click('[data-testid="import-done"]')

    # Verify both replaced
    ex_a = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{id_a}')"""
    )
    ex_b = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{id_b}')"""
    )
    assert ex_a["default_reps"] == 77
    assert ex_b["default_reps"] == 88

    _clean_all(page)


def test_same_user_collision_batch_skip_all(page: Page, app_url: str):
    """Batch 'Skip All' skips all collisions."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    id_a = _create_exercise_directly(page, "Skip A")
    id_b = _create_exercise_directly(page, "Skip B")

    exercises = [
        {
            "id": id_a,
            "title": "Skip A",
            "description": "Should not replace",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 99,
            "default_weight": 99,
            "created_by": local_uuid,
        },
        {
            "id": id_b,
            "title": "Skip B",
            "description": "Should not replace",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 99,
            "default_weight": 99,
            "created_by": local_uuid,
        },
    ]
    json_str = _build_export_json(exercises, "Local User", local_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_with_json(page, json_str)

    page.wait_for_selector(
        '[data-testid="import-collisions-step"]', timeout=5000
    )

    # Click Skip All (exercise-specific testid)
    page.click('[data-testid="exercise-skip-all"]')
    page.click('[data-testid="import-apply"]')

    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-skipped"]')).to_contain_text("2")

    page.click('[data-testid="import-done"]')

    # Verify originals unchanged
    ex_a = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{id_a}')"""
    )
    assert ex_a["default_reps"] == 10

    _clean_all(page)


def test_different_user_suffix_prompt(page: Page, app_url: str):
    """Importing from a different user shows the suffix prompt."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "other-user-uuid-9999"
    exercises = [
        {
            "id": "ext-exercise-id-1",
            "title": "External Push-ups",
            "description": "From another user",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 15,
            "default_weight": 0,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Jane Doe", other_uuid)

    _trigger_import_with_json(page, json_str)

    # Should see suffix step
    page.wait_for_selector('[data-testid="import-suffix-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-suffix-input"]')).to_be_visible()

    # Cancel
    page.click('[data-testid="import-cancel"]')

    _clean_all(page)


def test_different_user_import_with_suffix(page: Page, app_url: str):
    """Different-user import applies suffix to titles."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "other-user-uuid-8888"
    exercises = [
        {
            "id": "ext-exercise-suffix-1",
            "title": "Squats",
            "description": "External exercise",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 20,
            "default_weight": 50,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Bob Smith", other_uuid)

    _trigger_import_with_json(page, json_str)

    # Suffix step
    page.wait_for_selector('[data-testid="import-suffix-step"]', timeout=5000)

    # Clear and type new suffix
    page.fill('[data-testid="import-suffix-input"]', "BS")
    page.click('[data-testid="import-suffix-confirm"]')

    # Preview step
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)
    page.click('[data-testid="import-apply"]')

    # Report
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-imported"]')).to_be_visible()
    page.click('[data-testid="import-done"]')

    # Verify title has suffix
    page.wait_for_timeout(500)
    titles = page.locator('[data-testid="exercise-card-title"]')
    expect(titles).to_have_count(1)
    expect(titles.first).to_contain_text("Squats [BS]")

    _clean_all(page)


def test_different_user_update_same_id(page: Page, app_url: str):
    """Same external user, same ID is treated as an update (no new ID)."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "other-user-uuid-7777"
    ext_id = "ext-update-id-1"

    # First, create the exercise as if it was previously imported
    _create_exercise_directly(page, "Lunges [JD]", other_uuid, ext_id)

    # Also register the suffix
    page.evaluate(
        """async ([uuid]) => {
            await window.__repos.suffixes.create({
                user_uuid: uuid,
                user_name: 'John Doe',
                suffix: 'JD',
            });
        }""",
        [other_uuid],
    )

    # Import update with same external user + same ID
    exercises = [
        {
            "id": ext_id,
            "title": "Lunges",
            "description": "Updated version",
            "asymmetric": True,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 30,
            "default_weight": 15,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "John Doe", other_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_with_json(page, json_str)

    # Should skip suffix prompt (already registered) and go to preview
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)
    page.click('[data-testid="import-apply"]')

    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-exercises-updated"]')).to_be_visible()
    page.click('[data-testid="import-done"]')

    # Verify exercise was updated (same ID, new data)
    exercise = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{ext_id}')"""
    )
    assert exercise is not None
    assert exercise["default_reps"] == 30
    assert exercise["title"] == "Lunges [JD]"

    _clean_all(page)


def test_settings_export_filter_dialog(page: Page, app_url: str):
    """Settings export button opens the filter dialog."""
    _go_to_settings(page, app_url)
    _clean_all(page)

    page.click('[data-testid="settings-export"]')

    page.wait_for_selector('[data-testid="modal-card"]', timeout=5000)
    expect(page.locator('[data-testid="export-filter-exercises"]')).to_be_visible()
    expect(page.locator('[data-testid="export-filter-combos"]')).to_be_visible()
    expect(page.locator('[data-testid="export-filter-sessions"]')).to_be_visible()
    expect(page.locator('[data-testid="export-filter-collections"]')).to_be_visible()
    expect(page.locator('[data-testid="export-filter-execution-logs"]')).to_be_visible()
    expect(page.locator('[data-testid="export-filter-settings"]')).to_be_visible()

    # Close
    page.click('[data-testid="export-filter-cancel"]')
    page.wait_for_timeout(300)
    expect(page.locator('[data-testid="modal-overlay"]')).not_to_be_visible()

    _clean_all(page)


def test_settings_import_button(page: Page, app_url: str):
    """Settings import button opens file picker and handles import."""
    _go_to_settings(page, app_url)
    _clean_all(page)

    other_uuid = "settings-import-uuid-1111"
    exercises = [
        {
            "id": "settings-import-ex-1",
            "title": "Settings Import Exercise",
            "description": "Via settings",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 5,
            "default_weight": 10,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Settings User", other_uuid)

    _trigger_settings_import_with_json(page, json_str)

    # Should see suffix step (different user)
    page.wait_for_selector('[data-testid="import-suffix-step"]', timeout=5000)

    # Enter suffix and continue
    page.fill('[data-testid="import-suffix-input"]', "SU")
    page.click('[data-testid="import-suffix-confirm"]')

    # Preview
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)
    page.click('[data-testid="import-apply"]')

    # Report
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    page.click('[data-testid="import-done"]')

    # Verify exercise created
    exercise = page.evaluate(
        """async () => {
            const exercises = await window.__repos.exercises.getAll();
            return exercises.find(e => e.title === 'Settings Import Exercise [SU]');
        }"""
    )
    assert exercise is not None

    _clean_all(page)


def test_suffix_management_visible(page: Page, app_url: str):
    """Suffix management section is visible after an import from a different user."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    # Create a suffix entry directly
    page.evaluate(
        """async () => {
            await window.__repos.suffixes.create({
                user_uuid: 'suffix-mgmt-uuid-1',
                user_name: 'Alice',
                suffix: 'AL',
            });
        }"""
    )

    _go_to_settings(page, app_url)

    expect(page.locator('[data-testid="suffix-management"]')).to_be_visible()
    expect(page.locator('[data-testid="suffix-value"]')).to_contain_text("[AL]")

    _clean_all(page)


def test_suffix_rename_cascade(page: Page, app_url: str):
    """Renaming a suffix updates exercise titles that contain the old suffix."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "rename-test-uuid-5555"

    # Create suffix and exercise
    page.evaluate(
        f"""async () => {{
            await window.__repos.suffixes.create({{
                user_uuid: '{other_uuid}',
                user_name: 'Charlie',
                suffix: 'CH',
            }});
            await window.__repos.exercises.createWithId('rename-ex-1', {{
                title: 'Burpees [CH]',
                description: 'Test',
                asymmetric: false,
                alternate: false,
                image_urls: [],
                video_urls: [],
                default_reps: 10,
                default_weight: 0,
                created_by: '{other_uuid}',
            }});
        }}"""
    )

    _go_to_settings(page, app_url)

    # Start editing
    page.click('[data-testid="suffix-edit-btn"]')
    page.fill('[data-testid="suffix-edit-input"]', "CW")
    page.click('[data-testid="suffix-save-btn"]')

    page.wait_for_timeout(500)

    # Verify suffix updated in UI
    expect(page.locator('[data-testid="suffix-value"]')).to_contain_text("[CW]")

    # Verify exercise title was updated
    exercise = page.evaluate(
        """async () => await window.__repos.exercises.getById('rename-ex-1')"""
    )
    assert exercise["title"] == "Burpees [CW]"

    _clean_all(page)


def test_suffix_rename_collision_rolls_back(page: Page, app_url: str):
    """A rename that would create a duplicate title fails atomically: an error
    is shown and nothing (suffix entry or titles) was renamed."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "rename-atomic-uuid-6666"

    # Suffixed exercise 'Squats [CH]' plus a local 'Squats [CW]' that the
    # rename CH → CW would duplicate (exercise.title is UNIQUE)
    page.evaluate(
        f"""async () => {{
            await window.__repos.suffixes.create({{
                user_uuid: '{other_uuid}',
                user_name: 'Charlie',
                suffix: 'CH',
            }});
            await window.__repos.exercises.createWithId('atomic-ex-1', {{
                title: 'Squats [CH]',
                description: 'Test',
                asymmetric: false,
                alternate: false,
                image_urls: [],
                video_urls: [],
                default_reps: 10,
                default_weight: 0,
                created_by: '{other_uuid}',
            }});
            const userId = await window.__repos.settings.get('user_uuid')
            await window.__repos.exercises.createWithId('atomic-ex-2', {{
                title: 'Squats [CW]',
                description: 'Blocker',
                asymmetric: false,
                alternate: false,
                image_urls: [],
                video_urls: [],
                default_reps: 10,
                default_weight: 0,
                created_by: userId,
            }});
        }}"""
    )

    _go_to_settings(page, app_url)

    page.click('[data-testid="suffix-edit-btn"]')
    page.fill('[data-testid="suffix-edit-input"]', "CW")
    page.click('[data-testid="suffix-save-btn"]')

    # The rename must fail with a visible error
    expect(page.locator('[data-testid="suffix-edit-error"]')).to_be_visible()

    # ... and nothing must have been renamed (transaction rolled back)
    state = page.evaluate(
        f"""async () => {{
            const suffix = await window.__repos.suffixes.getByUuid('{other_uuid}')
            const ex = await window.__repos.exercises.getById('atomic-ex-1')
            return {{ suffix: suffix.suffix, title: ex.title }}
        }}"""
    )
    assert state["suffix"] == "CH", f"Suffix entry must be unchanged, got {state['suffix']}"
    assert state["title"] == "Squats [CH]", f"Exercise title must be unchanged, got {state['title']}"

    _clean_all(page)


def test_suffix_delete(page: Page, app_url: str):
    """Suffix can be deleted from settings."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    # Create suffix
    page.evaluate(
        """async () => {
            await window.__repos.suffixes.create({
                user_uuid: 'delete-suffix-uuid',
                user_name: 'Dave',
                suffix: 'DV',
            });
        }"""
    )

    _go_to_settings(page, app_url)

    expect(page.locator('[data-testid="suffix-management"]')).to_be_visible()

    # Delete with confirmation via modal dialog
    page.click('[data-testid="suffix-delete-btn"]')

    # Confirm deletion in the modal
    expect(page.locator('[data-testid="suffix-delete-confirm"]')).to_be_visible()
    page.click('[data-testid="suffix-delete-confirm"]')

    page.wait_for_timeout(500)

    # Suffix section should be hidden (no suffixes left)
    expect(page.locator('[data-testid="suffix-management"]')).not_to_be_visible()

    _clean_all(page)


# ============================================================
# Import As Mine Tests
# ============================================================


def test_import_as_mine_button_visible(page: Page, app_url: str):
    """Import-as-mine buttons are visible on exercise list and settings."""
    _go_to_exercises(page, app_url)
    _clean_all(page)
    expect(
        page.locator('[data-testid="exercise-import-as-mine-btn"]')
    ).to_be_visible()

    _go_to_settings(page, app_url)
    expect(
        page.locator('[data-testid="settings-import-as-mine"]')
    ).to_be_visible()

    _clean_all(page)


def test_import_as_mine_no_suffix_no_collision(page: Page, app_url: str):
    """Import-as-mine from a different user inserts exercises without suffix, owned by local user."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    other_uuid = "foreign-user-uuid-aaaa"

    exercises = [
        {
            "id": "as-mine-ex-1",
            "title": "Foreign Push-ups",
            "description": "From another user",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 15,
            "default_weight": 0,
            "created_by": other_uuid,
        },
        {
            "id": "as-mine-ex-2",
            "title": "Foreign Squats",
            "description": "From another user",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 20,
            "default_weight": 50,
            "created_by": other_uuid,
        },
    ]
    json_str = _build_export_json(exercises, "Foreign User", other_uuid)

    _trigger_import_as_mine_with_json(page, json_str)

    # Should go directly to preview (no suffix step)
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-suffix-step"]')).not_to_be_visible()

    page.click('[data-testid="import-apply"]')

    # Report
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-imported"]')).to_contain_text(
        "2"
    )
    page.click('[data-testid="import-done"]')

    # Verify exercises exist with original titles (no suffix)
    page.wait_for_timeout(500)
    titles = page.locator('[data-testid="exercise-card-title"]')
    expect(titles).to_have_count(2)
    expect(titles.nth(0)).to_contain_text("Foreign")
    expect(titles.nth(1)).to_contain_text("Foreign")

    # Verify created_by is the local user, not the foreign user
    ex1 = page.evaluate(
        """async () => await window.__repos.exercises.getById('as-mine-ex-1')"""
    )
    assert ex1["created_by"] == local_uuid
    assert ex1["title"] == "Foreign Push-ups"

    ex2 = page.evaluate(
        """async () => await window.__repos.exercises.getById('as-mine-ex-2')"""
    )
    assert ex2["created_by"] == local_uuid

    _clean_all(page)


def test_import_as_mine_collision_replace(page: Page, app_url: str):
    """Import-as-mine with ID collision shows collision step; replace overwrites exercise."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    other_uuid = "foreign-user-uuid-bbbb"
    ex_id = "as-mine-collision-1"

    # Create existing exercise with the same ID
    _create_exercise_directly(page, "Existing Plank", None, ex_id)

    exercises = [
        {
            "id": ex_id,
            "title": "Existing Plank",
            "description": "Imported version",
            "asymmetric": True,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 60,
            "default_weight": 0,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Foreign User", other_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_as_mine_with_json(page, json_str)

    # Should see collisions step (same-user logic)
    page.wait_for_selector(
        '[data-testid="import-collisions-step"]', timeout=5000
    )

    # Choose replace (exercise-specific testid)
    page.click('[data-testid="collision-replace-exercise-0"]')
    page.click('[data-testid="import-apply"]')

    # Report
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-exercises-replaced"]')).to_be_visible()
    page.click('[data-testid="import-done"]')

    # Verify exercise was replaced with new data, owned by local user
    exercise = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{ex_id}')"""
    )
    assert exercise["default_reps"] == 60
    assert exercise["asymmetric"] == 1  # SQLite stores bool as int
    assert exercise["created_by"] == local_uuid

    _clean_all(page)


def test_import_as_mine_collision_skip(page: Page, app_url: str):
    """Import-as-mine with ID collision and skip action keeps the original exercise."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "foreign-user-uuid-cccc"
    ex_id = "as-mine-skip-1"

    _create_exercise_directly(page, "Keep This Exercise", None, ex_id)

    exercises = [
        {
            "id": ex_id,
            "title": "Keep This Exercise",
            "description": "Should not replace",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 99,
            "default_weight": 99,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Foreign User", other_uuid)

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _trigger_import_as_mine_with_json(page, json_str)

    # Should see collisions step
    page.wait_for_selector(
        '[data-testid="import-collisions-step"]', timeout=5000
    )

    # Default is skip, just apply
    page.click('[data-testid="import-apply"]')

    # Report shows skipped
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-skipped"]')).to_be_visible()
    page.click('[data-testid="import-done"]')

    # Verify original unchanged
    exercise = page.evaluate(
        f"""async () => await window.__repos.exercises.getById('{ex_id}')"""
    )
    assert exercise["default_reps"] == 10

    _clean_all(page)


def test_import_as_mine_from_settings(page: Page, app_url: str):
    """Import-as-mine from settings page works without suffix prompt."""
    _go_to_settings(page, app_url)
    _clean_all(page)

    local_uuid = _get_local_uuid(page)
    other_uuid = "foreign-user-uuid-dddd"

    exercises = [
        {
            "id": "settings-as-mine-ex-1",
            "title": "Settings Mine Exercise",
            "description": "Via settings as mine",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 12,
            "default_weight": 25,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Foreign User", other_uuid)

    _trigger_settings_import_as_mine_with_json(page, json_str)

    # Should go to preview directly (no suffix)
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)
    page.click('[data-testid="import-apply"]')

    # Report
    page.wait_for_selector('[data-testid="import-report-step"]', timeout=5000)
    expect(page.locator('[data-testid="import-report-imported"]')).to_be_visible()
    page.click('[data-testid="import-done"]')

    # Verify exercise created and owned by local user
    exercise = page.evaluate(
        """async () => {
            const exercises = await window.__repos.exercises.getAll();
            return exercises.find(e => e.title === 'Settings Mine Exercise');
        }"""
    )
    assert exercise is not None
    assert exercise["created_by"] == local_uuid

    _clean_all(page)


def test_import_as_mine_dialog_title(page: Page, app_url: str):
    """Import-as-mine dialog shows the correct 'Import as Mine' title."""
    _go_to_exercises(page, app_url)
    _clean_all(page)

    other_uuid = "foreign-user-uuid-eeee"
    exercises = [
        {
            "id": "dialog-title-ex-1",
            "title": "Dialog Title Test",
            "description": "Test",
            "asymmetric": False,
            "alternate": False,
            "image_urls": [],
            "video_urls": [],
            "default_reps": 5,
            "default_weight": 0,
            "created_by": other_uuid,
        }
    ]
    json_str = _build_export_json(exercises, "Test User", other_uuid)

    _trigger_import_as_mine_with_json(page, json_str)

    # Wait for dialog
    page.wait_for_selector('[data-testid="import-preview-step"]', timeout=5000)

    # Verify the dialog title says "Import as Mine"
    modal_title = page.locator('[data-testid="modal-title"]')
    expect(modal_title).to_contain_text("Import as Mine")

    # Cancel
    page.click('[data-testid="import-cancel"]')

    _clean_all(page)
