"""
Step 1 — Database Layer Tests
- test_db_initializes: Database initializes without errors
- test_uuid_generated: A UUID is generated and stored in settings
- test_app_version_stored: App version is stored in settings
- test_default_settings: Default settings (user_name, language, theme) are created
- test_exercise_crud: Full CRUD cycle on exercises
- test_exercise_search: Search exercises by title
- test_data_survives_reload: Data persists across page reload
- test_settings_crud: Settings can be read and written
- test_schema_tables_exist: All expected tables exist in the schema
- test_foreign_keys_enabled: Foreign key constraints are enforced- test_combo_none_type: Combo can be created with type NONE
- test_session_item_repetitions: Session items support repetitions field
- test_collection_crud: Full CRUD cycle on collections (renamed from groups)
- test_execution_log_recent_and_count: getRecent and getCountSince work correctly"""
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 test_db_initializes(page: Page, app_url: str):
    """Database initializes and sets the data-db-ready attribute."""
    page.goto(app_url)
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'"
        " || document.documentElement.getAttribute('data-db-error') !== null",
        timeout=15000,
    )
    error = page.evaluate("document.documentElement.getAttribute('data-db-error')")
    assert error is None, f"DB initialization failed: {error}"


def test_uuid_generated(page: Page, app_url: str):
    """A UUID is generated on first launch and stored in settings."""
    _wait_for_db(page, app_url)
    uuid = page.evaluate("async () => await window.__repos.settings.get('user_uuid')")
    assert uuid is not None, "No user_uuid found in settings"
    # UUID v4 format: 8-4-4-4-12 hex chars
    import re
    assert re.match(
        r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
        uuid
    ), f"user_uuid is not a valid UUID: {uuid}"


def test_app_version_stored(page: Page, app_url: str):
    """App version from package.json is stored in settings."""
    _wait_for_db(page, app_url)
    version = page.evaluate("async () => await window.__repos.settings.get('app_version')")
    assert version is not None, "No app_version found in settings"
    assert version == "1.0.0", f"Unexpected app_version: {version}"


def test_default_settings(page: Page, app_url: str):
    """Default settings are created on first launch."""
    _wait_for_db(page, app_url)
    settings = page.evaluate("async () => await window.__repos.settings.getAll()")
    assert settings["user_name"] == "User", f"Unexpected user_name: {settings.get('user_name')}"
    assert settings["language"] == "en", f"Unexpected language: {settings.get('language')}"
    assert settings["theme"] == "light", f"Unexpected theme: {settings.get('theme')}"
    assert settings["db_created_at"] is not None, "db_created_at should be set on first launch"


def test_db_created_at_is_valid_iso_timestamp(page: Page, app_url: str):
    """db_created_at is stored as a valid ISO-8601 timestamp on first launch."""
    _wait_for_db(page, app_url)
    created_at = page.evaluate("async () => await window.__repos.settings.get('db_created_at')")
    assert created_at is not None, "db_created_at should be set"
    parsed = page.evaluate(f"new Date('{created_at}').getTime()")
    assert parsed > 0, f"db_created_at is not a valid date: {created_at}"


def test_exercise_crud(page: Page, app_url: str):
    """Full CRUD cycle: create, read, update, delete an exercise."""
    _wait_for_db(page, app_url)

    # Create
    ex_id = page.evaluate("""
        window.__repos.exercises.create({
            title: 'Push-ups',
            description: 'Basic push-up exercise',
            asymmetric: false,
            alternate: false,
            default_reps: 10,
            default_weight: 0,
            created_by: 'test'
        })
    """)
    assert ex_id is not None, "Exercise creation returned no ID"

    # Read
    exercise = page.evaluate(f"window.__repos.exercises.getById('{ex_id}')")
    assert exercise is not None, "Exercise not found after creation"
    assert exercise["title"] == "Push-ups"
    assert exercise["description"] == "Basic push-up exercise"
    assert exercise["asymmetric"] is False
    assert exercise["alternate"] is False
    assert exercise["default_reps"] == 10

    # Update
    page.evaluate(f"""
        window.__repos.exercises.update('{ex_id}', {{
            title: 'Diamond Push-ups',
            description: 'Advanced push-up variation',
            asymmetric: true,
            alternate: true,
            default_reps: 8,
            default_weight: 0
        }})
    """)
    updated = page.evaluate(f"window.__repos.exercises.getById('{ex_id}')")
    assert updated["title"] == "Diamond Push-ups"
    assert updated["default_reps"] == 8
    assert updated["asymmetric"] is True
    assert updated["alternate"] is True

    # Delete
    page.evaluate(f"window.__repos.exercises.delete('{ex_id}')")
    deleted = page.evaluate(f"window.__repos.exercises.getById('{ex_id}')")
    assert deleted is None, "Exercise still exists after deletion"


def test_exercise_search(page: Page, app_url: str):
    """Search exercises by title."""
    _wait_for_db(page, app_url)

    # Create a few exercises
    page.evaluate("""
        (async () => {
            await window.__repos.exercises.create({ title: 'Squat', description: '', created_by: 'test' });
            await window.__repos.exercises.create({ title: 'Bench Press', description: '', created_by: 'test' });
            await window.__repos.exercises.create({ title: 'Squat Jump', description: '', created_by: 'test' });
        })()
    """)

    results = page.evaluate("window.__repos.exercises.search('Squat')")
    assert len(results) >= 2, f"Expected at least 2 results for 'Squat', got {len(results)}"
    titles = [r["title"] for r in results]
    assert "Squat" in titles
    assert "Squat Jump" in titles

    # Cleanup
    page.evaluate("""
        (async () => {
            const all = await window.__repos.exercises.getAll();
            for (const e of all) await window.__repos.exercises.delete(e.id);
        })()
    """)


def test_data_survives_reload(page: Page, app_url: str):
    """Data persists across page reload (OPFS persistence)."""
    _wait_for_db(page, app_url)

    # Create an exercise
    ex_id = page.evaluate("""
        window.__repos.exercises.create({
            title: 'Persistent Exercise',
            description: 'Should survive reload',
            created_by: 'test'
        })
    """)

    # Reload the page
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )

    # Verify exercise still exists
    exercise = page.evaluate(f"window.__repos.exercises.getById('{ex_id}')")
    assert exercise is not None, "Exercise lost after reload — persistence may not be working"
    assert exercise["title"] == "Persistent Exercise"

    # Cleanup
    page.evaluate(f"window.__repos.exercises.delete('{ex_id}')")


def test_settings_crud(page: Page, app_url: str):
    """Settings can be read and updated."""
    _wait_for_db(page, app_url)

    # Read existing
    original = page.evaluate("async () => await window.__repos.settings.get('user_name')")
    assert original == "User"

    # Update
    page.evaluate("async () => { await window.__repos.settings.set('user_name', 'TestUser') }")
    updated = page.evaluate("async () => await window.__repos.settings.get('user_name')")
    assert updated == "TestUser", f"Expected 'TestUser', got '{updated}'"

    # Restore
    page.evaluate("async () => { await window.__repos.settings.set('user_name', 'User') }")


def test_schema_tables_exist(page: Page, app_url: str):
    """All expected tables exist in the database."""
    _wait_for_db(page, app_url)

    tables = page.evaluate("""
        window.__db.selectAll(
            "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
        )
    """)
    table_names = [t["name"] for t in tables]
    expected = [
        "collection", "collection_session", "combo", "combo_exercise",
        "execution_log", "execution_log_item", "exercise",
        "schema_version", "session", "session_item", "settings", "suffix"
    ]
    for name in expected:
        assert name in table_names, f"Table '{name}' not found. Found: {table_names}"


def test_foreign_keys_enabled(page: Page, app_url: str):
    """Foreign key constraints are enforced."""
    _wait_for_db(page, app_url)

    # Try to insert a session_item referencing a non-existent session
    error = page.evaluate("""
        (async () => {
            try {
                await window.__db.run(
                    "INSERT INTO session_item (session_id, item_id, item_type, position) VALUES (?, ?, ?, ?)",
                    ['nonexistent', 'item1', 'exercise', 0]
                );
                return null;
            } catch (e) {
                return e.message;
            }
        })()
    """)
    assert error is not None, "Foreign key constraint was not enforced"


def test_combo_none_type(page: Page, app_url: str):
    """Combo can be created with type NONE."""
    _wait_for_db(page, app_url)

    combo_id = page.evaluate("""
        window.__repos.combos.create({
            title: 'Simple Combo',
            description: 'A combo with no special type',
            type: 'NONE',
            created_by: 'test'
        })
    """)
    assert combo_id is not None, "Combo creation returned no ID"

    combo = page.evaluate(f"window.__repos.combos.getById('{combo_id}')")
    assert combo is not None, "Combo not found after creation"
    assert combo["type"] == "NONE"

    # Cleanup
    page.evaluate(f"window.__repos.combos.delete('{combo_id}')")


def test_session_item_repetitions(page: Page, app_url: str):
    """Session items support a repetitions (series) field."""
    _wait_for_db(page, app_url)

    # Create a session and an exercise
    session_id = page.evaluate("""
        window.__repos.sessions.create({
            title: 'Repetitions Test Session',
            description: '',
            created_by: 'test'
        })
    """)
    ex_id = page.evaluate("""
        window.__repos.exercises.create({
            title: 'Burpees',
            description: '',
            created_by: 'test'
        })
    """)

    # Set items with repetitions
    page.evaluate(f"""
        window.__repos.sessions.setItems('{session_id}', [
            {{ item_id: '{ex_id}', item_type: 'exercise', repetitions: 3 }}
        ])
    """)

    session = page.evaluate(f"window.__repos.sessions.getById('{session_id}')")
    assert session["items"][0]["repetitions"] == 3, f"Expected repetitions=3, got {session['items'][0].get('repetitions')}"

    # Default repetitions should be 1
    page.evaluate(f"""
        window.__repos.sessions.setItems('{session_id}', [
            {{ item_id: '{ex_id}', item_type: 'exercise' }}
        ])
    """)
    session = page.evaluate(f"window.__repos.sessions.getById('{session_id}')")
    assert session["items"][0]["repetitions"] == 1, "Default repetitions should be 1"

    # Cleanup
    page.evaluate(f"window.__repos.sessions.delete('{session_id}')")
    page.evaluate(f"window.__repos.exercises.delete('{ex_id}')")


def test_collection_crud(page: Page, app_url: str):
    """Full CRUD cycle on collections (renamed from groups)."""
    _wait_for_db(page, app_url)

    # Create
    coll_id = page.evaluate("""
        window.__repos.collections.create({
            label: 'Morning Routine',
            created_by: 'test'
        })
    """)
    assert coll_id is not None, "Collection creation returned no ID"

    # Read
    coll = page.evaluate(f"window.__repos.collections.getById('{coll_id}')")
    assert coll is not None, "Collection not found after creation"
    assert coll["label"] == "Morning Routine"

    # Update
    page.evaluate(f"""
        window.__repos.collections.update('{coll_id}', {{ label: 'Evening Routine' }})
    """)
    updated = page.evaluate(f"window.__repos.collections.getById('{coll_id}')")
    assert updated["label"] == "Evening Routine"

    # Delete
    page.evaluate(f"window.__repos.collections.delete('{coll_id}')")
    deleted = page.evaluate(f"window.__repos.collections.getById('{coll_id}')")
    assert deleted is None, "Collection still exists after deletion"


def test_execution_log_recent_and_count(page: Page, app_url: str):
    """getRecent returns last N logs with session title; getCountSince counts logs after a date."""
    _wait_for_db(page, app_url)

    # Create a session
    session_id = page.evaluate("""
        window.__repos.sessions.create({
            title: 'Stats Test Session',
            description: '',
            created_by: 'test'
        })
    """)

    # Create execution logs with different dates
    page.evaluate(f"""
        (async () => {{
            await window.__repos.executionLogs.create({{ session_id: '{session_id}', started_at: '2026-03-10T10:00:00Z', finished_at: '2026-03-10T11:00:00Z' }});
            await window.__repos.executionLogs.create({{ session_id: '{session_id}', started_at: '2026-03-12T10:00:00Z', finished_at: '2026-03-12T11:00:00Z' }});
            await window.__repos.executionLogs.create({{ session_id: '{session_id}', started_at: '2026-03-14T10:00:00Z', finished_at: '2026-03-14T11:00:00Z' }});
        }})()
    """)

    # getRecent
    recent = page.evaluate("window.__repos.executionLogs.getRecent(2)")
    assert len(recent) == 2, f"Expected 2 recent logs, got {len(recent)}"
    assert recent[0]["session_title"] == "Stats Test Session"
    # Most recent first
    assert recent[0]["started_at"] > recent[1]["started_at"]

    # getCountSince
    count = page.evaluate("window.__repos.executionLogs.getCountSince('2026-03-11T00:00:00Z')")
    assert count == 2, f"Expected 2 logs since March 11, got {count}"

    # Cleanup
    page.evaluate(f"window.__repos.sessions.delete('{session_id}')")
