"""Unit tests for E2E harness models and helper utilities."""
import pytest
from unittest.mock import patch, MagicMock
import time
from tests.e2e.models import ErrorRecord, ModeConfig, diff_errors
import tests.e2e.modes.cloud as cloud_mod  # imported early so load_dotenv runs before any monkeypatch


def test_error_record_equality():
    a = ErrorRecord(type="exception", message="boom", element_html="<div>boom</div>")
    b = ErrorRecord(type="exception", message="boom", element_html="<div>boom</div>")
    assert a == b


def test_error_record_inequality():
    a = ErrorRecord(type="exception", message="boom", element_html="")
    b = ErrorRecord(type="alert", message="boom", element_html="")
    assert a != b


def test_diff_errors_returns_new_only():
    before = [ErrorRecord("exception", "old error", "")]
    after = [
        ErrorRecord("exception", "old error", ""),
        ErrorRecord("alert", "new error", ""),
    ]
    result = diff_errors(before, after)
    assert result == [ErrorRecord("alert", "new error", "")]


def test_diff_errors_empty_when_no_change():
    errors = [ErrorRecord("exception", "x", "")]
    assert diff_errors(errors, errors) == []


def test_diff_errors_empty_before():
    after = [ErrorRecord("alert", "boom", "")]
    assert diff_errors([], after) == after


def test_mode_config_expected_failure_match():
    config = ModeConfig(
        name="demo",
        base_url="http://localhost:8504",
        auth_setup=lambda ctx: None,
        expected_failures=["Fetch*", "Generate Cover Letter"],
        results_dir=None,
        settings_tabs=["👤 My Profile"],
    )
    assert config.matches_expected_failure("Fetch New Jobs")
    assert config.matches_expected_failure("Generate Cover Letter")
    assert not config.matches_expected_failure("View Jobs")


def test_mode_config_no_expected_failures():
    config = ModeConfig(
        name="local",
        base_url="http://localhost:8502",
        auth_setup=lambda ctx: None,
        expected_failures=[],
        results_dir=None,
        settings_tabs=[],
    )
    assert not config.matches_expected_failure("Fetch New Jobs")


def test_get_jwt_strategy_b_fallback(monkeypatch):
    """Falls back to persistent JWT when no email env var set."""
    monkeypatch.delenv("E2E_DIRECTUS_EMAIL", raising=False)
    monkeypatch.setenv("E2E_DIRECTUS_JWT", "persistent.jwt.token")
    cloud_mod._token_cache.update({"token": None, "expires_at": 0.0})
    assert cloud_mod._get_jwt() == "persistent.jwt.token"


def test_get_jwt_strategy_b_raises_if_no_token(monkeypatch):
    """Raises if neither email nor JWT env var is set."""
    monkeypatch.delenv("E2E_DIRECTUS_EMAIL", raising=False)
    monkeypatch.delenv("E2E_DIRECTUS_JWT", raising=False)
    cloud_mod._token_cache.update({"token": None, "expires_at": 0.0})
    with pytest.raises(RuntimeError, match="Cloud mode requires"):
        cloud_mod._get_jwt()


def test_get_jwt_strategy_a_login(monkeypatch):
    """Strategy A: calls Directus /auth/login and caches token."""
    monkeypatch.setenv("E2E_DIRECTUS_EMAIL", "e2e@circuitforge.tech")
    monkeypatch.setenv("E2E_DIRECTUS_PASSWORD", "testpass")
    monkeypatch.setenv("E2E_DIRECTUS_URL", "http://fake-directus:8055")
    cloud_mod._token_cache.update({"token": None, "expires_at": 0.0})

    mock_resp = MagicMock()
    mock_resp.json.return_value = {"data": {"access_token": "fresh.jwt", "expires": 900_000}}
    mock_resp.raise_for_status = lambda: None

    with patch("tests.e2e.modes.cloud.requests.post", return_value=mock_resp) as mock_post:
        token = cloud_mod._get_jwt()

    assert token == "fresh.jwt"
    mock_post.assert_called_once()
    assert cloud_mod._token_cache["token"] == "fresh.jwt"


def test_get_jwt_uses_cache(monkeypatch):
    """Returns cached token if not yet expired."""
    monkeypatch.setenv("E2E_DIRECTUS_EMAIL", "e2e@circuitforge.tech")
    cloud_mod._token_cache.update({"token": "cached.jwt", "expires_at": time.time() + 500})
    with patch("tests.e2e.modes.cloud.requests.post") as mock_post:
        token = cloud_mod._get_jwt()
    assert token == "cached.jwt"
    mock_post.assert_not_called()


def test_get_page_errors_finds_exceptions(monkeypatch):
    """get_page_errors returns ErrorRecord for stException elements."""
    from tests.e2e.conftest import get_page_errors

    mock_el = MagicMock()
    mock_el.text_content.return_value = "RuntimeError: boom"
    mock_el.inner_html.return_value = "<div>RuntimeError: boom</div>"

    mock_page = MagicMock()
    mock_page.query_selector_all.side_effect = lambda sel: (
        [mock_el] if "stException" in sel else []
    )

    errors = get_page_errors(mock_page)
    assert len(errors) == 1
    assert errors[0].type == "exception"
    assert "boom" in errors[0].message


def test_get_page_errors_finds_alert_errors(monkeypatch):
    """get_page_errors returns ErrorRecord for stAlert with stAlertContentError child."""
    from tests.e2e.conftest import get_page_errors

    mock_child = MagicMock()
    mock_el = MagicMock()
    mock_el.query_selector.return_value = mock_child
    mock_el.text_content.return_value = "Something went wrong"
    mock_el.inner_html.return_value = "<div>Something went wrong</div>"

    mock_page = MagicMock()
    mock_page.query_selector_all.side_effect = lambda sel: (
        [] if "stException" in sel else [mock_el]
    )

    errors = get_page_errors(mock_page)
    assert len(errors) == 1
    assert errors[0].type == "alert"


def test_get_page_errors_ignores_non_error_alerts(monkeypatch):
    """get_page_errors does NOT flag st.warning() or st.info() alerts."""
    from tests.e2e.conftest import get_page_errors

    mock_el = MagicMock()
    mock_el.query_selector.return_value = None
    mock_el.inner_text.return_value = "Just a warning"

    mock_page = MagicMock()
    mock_page.query_selector_all.side_effect = lambda sel: (
        [] if "stException" in sel else [mock_el]
    )

    errors = get_page_errors(mock_page)
    assert errors == []


def test_get_console_errors_filters_noise():
    """get_console_errors filters benign Streamlit WebSocket reconnect messages."""
    from tests.e2e.conftest import get_console_errors

    messages = [
        MagicMock(type="error", text="WebSocket connection closed"),
        MagicMock(type="error", text="TypeError: cannot read property"),
        MagicMock(type="log", text="irrelevant"),
    ]
    errors = get_console_errors(messages)
    assert errors == ["TypeError: cannot read property"]
