*** Settings ***
Resource    ${CURDIR}/../resources/common.resource
Resource    ${CURDIR}/../resources/auth.resource

Suite Setup    Login As Admin

*** Test Cases ***
Config Field Edit Persists After Reload
    [Documentation]    Verifies auto-save round-trip: UI edit → debounced PATCH → reload rehydration.
    ...
    ...    - Restores original value in teardown so subsequent tests are not affected.
    ...    - Runs last in suite ordering to avoid destabilising fail2ban health for other tests.
    [Teardown]    Run Keyword And Ignore Error    Restore Original Ban Time

    # Step 1 — navigate to config page and click Jails tab.
    Go To    ${FRONTEND_URL}/config
    Wait For Load State    domcontentloaded
    Sleep    5s
    # Use JS click for the Jails tab — avoids Playwright role=tab selector timing issues
    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const tabs = document.querySelectorAll('[role="tab"]');
    ...        for (const tab of tabs) {
    ...            if (tab.getAttribute('data-testid') === 'jails-tab' ||
    ...                (tab.textContent?.trim() === 'Jails' && tab.getAttribute('aria-label') === 'Jails')) {
    ...                tab.click(); return;
    ...            }
    ...        }
    ...        // Fallback: click any tab whose visible text includes 'Jails'
    ...        for (const tab of tabs) {
    ...            if (tab.textContent?.trim() === 'Jails') { tab.click(); return; }
    ...        }
    ...    }
    Sleep    5s

    # Step 2 — wait for jail list to load (retry until options appear).
    FOR    ${i}    IN RANGE    1    21
        ${opts}=    Get Elements    css=[role="option"]
        ${count}=    Get Length    ${opts}
        IF    ${count} > 0
            BREAK
        END
        Sleep    2s
    END
    Log    Jail options loaded: ${count} found.

    # Scroll the list pane to top to ensure options are visible
    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const listPane = document.querySelector('[role="listbox"]');
    ...        if (listPane) listPane.scrollTop = 0;
    ...    }
    Sleep    1s

    # Step 3 — find active jail name via JS (avoids strict-mode selector issues with virtual lists)
    ${active_jail_name}=    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const items = document.querySelectorAll('[role="option"]');
    ...        let activeCount = 0;
    ...        let firstActiveName = null;
    ...        items.forEach(el => {
    ...            const badge = el.querySelector('[class*="Badge"]');
    ...            if (badge && badge.textContent?.trim() === 'Active') {
    ...                activeCount++;
    ...                if (firstActiveName === null) firstActiveName = el.getAttribute('data-name');
    ...            }
    ...        });
    ...        return { active: activeCount, firstActiveName };
    ...    }
    Log    Active jail info: ${active_jail_name}

    IF    ${active_jail_name['active']} == 0
        Log    No active jails found. Test requires at least one active jail to verify auto-save.
        Skip    Test requires at least one active jail. Activate a jail via the UI or API first.
    END

    Set Suite Variable    ${ACTIVE_JAIL_NAME}    ${active_jail_name['firstActiveName']}
    Log    Active jail name: ${ACTIVE_JAIL_NAME}

    # Click the active jail directly by name using JS (bypasses Playwright strict-mode selector conflicts)
    ${click_result}=    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const opts = document.querySelectorAll('[role="option"]');
    ...        for (const opt of opts) {
    ...            if (opt.getAttribute('data-name') === '${ACTIVE_JAIL_NAME}') {
    ...                opt.click();
    ...                return 'CLICKED:' + opt.getAttribute('data-name');
    ...            }
    ...        }
    ...        return 'NOT_FOUND';
    ...    }
    Log    JS click result: ${click_result}
    Sleep    5s

    # Verify ban_time input appeared
    ${has_bantime}=    Evaluate JavaScript    ${None}
    ...    () => {
    ...        return document.querySelector('input[data-field="ban_time"]') !== null;
    ...    }
    Log    Has editable ban_time input: ${has_bantime}

    IF    '${has_bantime}' != 'True'
        Fatal Error    TEST_BODY: ban_time input not found for jail ${ACTIVE_JAIL_NAME}.
    END

    # Get original ban_time value
    ${ban_time_value}=    Get Attribute    css=input[data-field="ban_time"]    value
    Set Suite Variable    ${ORIGINAL_BANTIME}    ${ban_time_value}
    Log    Original bantime: ${ORIGINAL_BANTIME}

    # Step 4 — edit ban_time to 7200 using keyboard press (bypasses React synthetic event issues with Fill Text)
    # Clear the field first by selecting all text
    Click    css=input[data-field="ban_time"]
    Sleep    500ms
    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const input = document.querySelector('input[data-field="ban_time"]');
    ...        if (input) {
    ...            input.select();
    ...            input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));
    ...        }
    ...    }
    Sleep    500ms
    Fill Text    css=input[data-field="ban_time"]    7200
    Sleep    1s
    # Verify the fill worked by checking value via JS
    ${fill_value}=    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const el = document.querySelector('input[data-field="ban_time"]');
    ...        return el ? el.value : 'NOT_FOUND';
    ...    }
    Log    Value after fill attempt: ${fill_value}

    # Step 5 — wait for auto-save indicator.
    # Also check that the fill actually changed the value (auto-save needs the value to differ from server state)
    ${fill_check}=    Get Attribute    css=input[data-field="ban_time"]    value
    Log    Value after fill: ${fill_check}
    ${saved}=    Set Variable    ${FALSE}
    FOR    ${i}    IN RANGE    1    31
        ${status_visible}=    Run Keyword And Return Status    Wait For Elements State    css=[role="status"]    visible    timeout=2s
        IF    ${status_visible}
            ${status_text}=    Evaluate JavaScript    ${None}
            ...    () => {
            ...        const el = document.querySelector('[role="status"]');
            ...        return el ? el.textContent : '';
            ...    }
            Log    Status element text: ${status_text}
            IF    'saved' in '${status_text}'.toLowerCase()
                ${saved}=    Set Variable    ${TRUE}
                BREAK
            END
        END
        Sleep    1s
    END
    Log    Auto-save confirmed: ${saved}

    # Step 6 — verify via API.
    ${resp}=    Run Keyword And Ignore Error    GET    ${BACKEND_URL}/api/jails
    ${verify_ok}=    Run Keyword And Return Status    Should Contain    ${resp.text}    7200
    IF    ${verify_ok}
        Log    API verification passed: 7200 found in jail configs.
    ELSE
        Log    API verification skipped (rate-limited or error): ${resp}
    END

    # Step 7 — reload and verify persistence.
    Reload
    Wait For Load State    domcontentloaded
    Sleep    5s

    # Re-authenticate if session was lost after reload
    ${needs_auth}=    Evaluate JavaScript    ${None}
    ...    async () => {
    ...        // Check current URL - if on login page, need to re-auth
    ...        if (window.location.pathname.includes('/login')) return true;
    ...        // Check if we can fetch authenticated API
    ...        try {
    ...            const res = await fetch('/api/v1/auth/login', {
    ...                method: 'POST',
    ...                headers: { 'Content-Type': 'application/json' },
    ...                body: JSON.stringify({ password: 'Hallo123!' }),
    ...                credentials: 'include'
    ...            });
    ...            return !res.ok;
    ...        } catch(e) { return true; }
    ...    }
    Log    Needs re-authentication: ${needs_auth}

    IF    ${needs_auth}
        Evaluate JavaScript    ${None}
        ...    async () => {
        ...        await new Promise(r => setTimeout(r, 2000));
        ...        const res = await fetch('/api/v1/auth/login', {
        ...            method: 'POST',
        ...            headers: { 'Content-Type': 'application/json' },
        ...            body: JSON.stringify({ password: 'Hallo123!' }),
        ...            credentials: 'include'
        ...        });
        ...        return res.ok;
        ...    }
        Evaluate JavaScript    ${None}    () => sessionStorage.setItem('bangui_authenticated', 'true')
    END

    # Check if Jails tab is already visible, otherwise go to config page
    ${tab_visible}=    Run Keyword And Return Status    Wait For Elements State    role=tab[name=Jails]    visible    timeout=3s
    IF    not ${tab_visible}
        Go To    ${FRONTEND_URL}/config
        Wait For Load State    domcontentloaded
        Sleep    5s
    END

    # Use JS click for the Jails tab — avoids Playwright role=tab selector timing issues
    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const tabs = document.querySelectorAll('[role="tab"]');
    ...        for (const tab of tabs) {
    ...            if (tab.textContent?.trim() === 'Jails') { tab.click(); return; }
    ...        }
    ...    }
    Sleep    5s
    FOR    ${i}    IN RANGE    1    21
        ${opts}=    Get Elements    css=[role="option"]
        ${count}=    Get Length    ${opts}
        IF    ${count} > 0
            BREAK
        END
        Sleep    2s
    END

    # Re-click the active jail by name to verify reloaded value.
    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const opts = document.querySelectorAll('[role="option"]');
    ...        for (const opt of opts) {
    ...            if (opt.getAttribute('data-name') === '${ACTIVE_JAIL_NAME}') {
    ...                opt.scrollIntoView({ behavior: 'instant', block: 'center' });
    ...                opt.click();
    ...                return 'CLICKED:' + opt.getAttribute('data-name');
    ...            }
    ...        }
    ...        // Fallback: click first option
    ...        if (opts.length > 0) { opts[0].click(); return 'FALLBACK:' + opts[0].getAttribute('data-name'); }
    ...        return 'NO_OPTS';
    ...    }
    Log    Jail re-click result: ${click_result}
    Sleep    8s

    # Debug: check if detail panel has rendered
    ${detail_html}=    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const panel = document.querySelector('[data-testid="jail-detail-panel"]') ||
    ...                        document.querySelector('.f22iagw') ||
    ...                        document.querySelector('[class*="fieldRow"]');
    ...        const bantimeInput = document.querySelector('input[data-field="ban_time"]');
    ...        const allInputs = document.querySelectorAll('input');
    ...        return {
    ...            hasDetailPanel: !!panel,
    ...            hasBantimeInput: !!bantimeInput,
    ...            inputCount: allInputs.length,
    ...            bantimeValue: bantimeInput ? bantimeInput.value : 'NOT_FOUND',
    ...            firstInputDataField: allInputs[0]?.getAttribute('data-field') || 'none'
    ...        };
    ...    }
    Log    Detail panel state after re-click: ${detail_html}

    ${reloaded}=    Set Variable    ${EMPTY}
    FOR    ${i}    IN RANGE    1    31
        ${input_visible}=    Run Keyword And Return Status    Wait For Elements State    css=input[data-field="ban_time"]    visible    timeout=5s
        IF    ${input_visible}
            ${reloaded}=    Evaluate JavaScript    ${None}
            ...    () => {
            ...        const el = document.querySelector('input[data-field="ban_time"]');
            ...        return el ? el.value : 'NOT_FOUND';
            ...    }
            Log    Reloaded bantime at attempt ${i}: ${reloaded}
            IF    '${reloaded}' != '${EMPTY}' and '${reloaded}' != 'None' and '${reloaded}' != 'FAIL' and '${reloaded}' != 'NOT_FOUND'
                BREAK
            END
        END
        Sleep    1s
    END
    Log    Reloaded bantime: ${reloaded}
    IF    '${reloaded}' == '${EMPTY}' or '${reloaded}' == 'None'
        Fatal Error    TEST_BODY: Ban Time input not found after reload.
    END
    Should Be Equal As Strings    ${reloaded}    7200
    Log    Reload verification passed — value persisted.


*** Keywords ***
Restore Original Ban Time
    [Documentation]    Restore jail's original ban_time so subsequent tests are unaffected.
    ${has_original}=    Run Keyword And Return Status    Should Not Be Empty    ${ORIGINAL_BANTIME}
    IF    not ${has_original}
        Log    No original ban_time to restore.
        RETURN
    END
    Go To    ${FRONTEND_URL}/config
    Wait For Load State    domcontentloaded
    Sleep    5s
    Run Keyword And Ignore Error    Click    role=tab[name=Jails]
    Sleep    3s
    Run Keyword And Ignore Error    Evaluate JavaScript    ${None}
    ...    () => {
    ...        const listPane = document.querySelector('[role="listbox"]');
    ...        if (listPane) listPane.scrollTop = 0;
    ...        const opts = document.querySelectorAll('[role="option"]');
    ...        if (opts.length > 0) opts[0].click();
    ...    }
    Sleep    3s
    Run Keyword And Ignore Error    Fill Text    css=input[aria-label="Ban Time (s)"]    ${ORIGINAL_BANTIME}
    Sleep    3s
    Run Keyword And Ignore Error    Wait For Elements State    css=[role="status"]:has-text("Saved")    visible    timeout=15s
    Log    Teardown restore ${ORIGINAL_BANTIME}