"""
Step 9 — Statistics
- test_stats_shows_creation_date: StatsView displays the database creation date
- test_stats_shows_empty_when_no_date: StatsView shows empty message when no creation date
- test_stats_date_format_english: Date is formatted as MM/DD/YYYY in English
- test_stats_date_format_french: Date is formatted as DD/MM/YYYY in French
- test_stats_date_format_danish: Date is formatted as DD.MM.YYYY in Danish
- test_stats_date_format_updates_on_language_change: Switching language updates the date format reactively
- test_stats_date_range_tabs_visible: All four date range tabs are rendered
- test_stats_summary_cards_zero_when_no_data: Summary cards show 0 when no execution logs exist
- test_stats_summary_cards_with_data: Summary cards show correct counts after seeding logs
- test_stats_date_range_filter: Changing date range updates the sessions count
- test_stats_frequency_no_data_message: Frequency no-data message shown when no sessions
- test_stats_volume_no_data_message: Volume no-data message shown when no sessions with weight
- test_stats_progression_no_data_message: Progression no-data shown before exercise selected
- test_stats_frequency_chart_renders: Frequency chart canvas renders after seeding data
- test_stats_volume_chart_renders: Volume chart canvas renders when sessions have weight data
- test_stats_progression_chart_renders: Progression chart renders after selecting an exercise
- test_stats_streak_consecutive_days: Streak counts consecutive days with sessions
- test_stats_streak_ignores_aborted_sessions: Aborted sessions do not extend the streak
"""
from datetime import datetime, timedelta, timezone

import pytest
from playwright.sync_api import Page, expect

_KNOWN_DATE = "2026-03-15T10:00:00.000Z"


# ── Helpers ───────────────────────────────────────────────────────────────────


def _wait_for_db(page: Page, app_url: str):
    page.goto(app_url)
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )


def _set_date_and_reload(page: Page, app_url: str, date: str | None):
    _wait_for_db(page, app_url)
    page.evaluate("async () => { await window.__repos.settings.set('language', 'en') }")
    if date is None:
        page.evaluate("async () => { await window.__repos.settings.set('db_created_at', null) }")
    else:
        page.evaluate(
            f"async () => {{ await window.__repos.settings.set('db_created_at', '{date}') }}"
        )
    page.wait_for_function(
        "window.__repos.settings.get('language').then(v => v === 'en')",
        timeout=5000,
    )
    if date is None:
        page.wait_for_function(
            "window.__repos.settings.get('db_created_at')"
            ".then(v => v === null || v === undefined || v === '')",
            timeout=5000,
        )
    else:
        page.wait_for_function(
            f"window.__repos.settings.get('db_created_at').then(v => v === '{date}')",
            timeout=5000,
        )


def _go_to_stats(page: Page):
    """Navigate to the stats page and wait for the view to finish loading."""
    page.click('a[href="#/stats"]')
    page.wait_for_url("**/#/stats", timeout=5000)
    page.wait_for_selector('.stats-view', timeout=5000)
    # Wait for async onMounted to finish (cards rendered)
    page.wait_for_selector('[data-testid="stats-total-sessions"]', timeout=5000)


def _iso_days_ago(days: int) -> str:
    """Return an ISO-8601 UTC timestamp for N days ago."""
    dt = datetime.now(timezone.utc) - timedelta(days=days)
    return dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')


def _create_exercise(page: Page, title: str) -> str:
    return page.evaluate(
        f"""async () => {{
            const userId = await window.__repos.settings.get('user_uuid')
            return window.__repos.exercises.create({{
                title: {repr(title)},
                description: '',
                asymmetric: false,
                alternate: false,
                image_urls: '[]',
                video_urls: '[]',
                default_reps: 10,
                default_weight: 0,
                created_by: userId
            }})
        }}"""
    )


def _create_session(page: Page, title: str) -> str:
    return page.evaluate(
        f"""async () => {{
            const userId = await window.__repos.settings.get('user_uuid')
            return window.__repos.sessions.create({{
                title: {repr(title)}, description: '', created_by: userId
            }})
        }}"""
    )


def _create_log_with_items(
    page: Page,
    session_id: str,
    exercise_id: str,
    started_at: str,
    reps: int = 10,
    weight: float = 0.0,
    completed: bool = True,
    status: str = 'completed',
) -> str:
    """Seed one execution log with one item."""
    finished_at = started_at  # same timestamp is fine for tests
    return page.evaluate(
        f"""async () => {{
            const logId = await window.__repos.executionLogs.create({{
                session_id: '{session_id}',
                started_at: '{started_at}',
                finished_at: '{finished_at}',
                status: {repr(status)}
            }})
            await window.__repos.executionLogs.addItem({{
                log_id: logId,
                exercise_id: '{exercise_id}',
                reps_done: {reps},
                weight_used: {weight},
                side: null,
                completed: {'true' if completed else 'false'},
                timestamp: '{started_at}'
            }})
            return logId
        }}"""
    )


def _delete_all_logs(page: Page):
    """Remove all execution logs (to reset state between tests)."""
    page.evaluate(
        """async () => {
            const logs = await window.__repos.executionLogs.getAll()
            for (const log of logs) {
                await window.__repos.executionLogs.delete(log.id)
            }
        }"""
    )


def _delete_all_exercises(page: Page):
    page.evaluate(
        """async () => {
            const items = await window.__repos.exercises.getAll()
            for (const item of items) {
                await window.__repos.exercises.delete(item.id)
            }
        }"""
    )


def _delete_all_sessions(page: Page):
    page.evaluate(
        """async () => {
            const items = await window.__repos.sessions.getAll()
            for (const item of items) {
                await window.__repos.sessions.delete(item.id)
            }
        }"""
    )


# ── Creation date tests (unchanged logic) ────────────────────────────────────


def test_stats_shows_creation_date(page: Page, app_url: str):
    """StatsView displays the database creation date."""
    _set_date_and_reload(page, app_url, _KNOWN_DATE)
    _go_to_stats(page)

    creation_msg = page.locator('[data-testid="stats-using-since"]')
    expect(creation_msg).to_be_visible()
    text = creation_msg.text_content()
    assert 'You are using TrainUs since' in text, f"Expected creation date message, got: {text}"


def test_stats_shows_empty_when_no_date(page: Page, app_url: str):
    """StatsView still loads when no creation date is set."""
    _set_date_and_reload(page, app_url, None)
    _go_to_stats(page)

    assert page.locator('[data-testid="stats-using-since"]').count() == 0

    # Restore a valid date for subsequent tests
    page.evaluate(
        f"async () => {{ await window.__repos.settings.set('db_created_at', '{_KNOWN_DATE}') }}"
    )
    page.wait_for_function(
        f"window.__repos.settings.get('db_created_at').then(v => v === '{_KNOWN_DATE}')",
        timeout=5000,
    )


def test_stats_date_format_english(page: Page, app_url: str):
    """Date is formatted as MM/DD/YYYY in English."""
    _set_date_and_reload(page, app_url, _KNOWN_DATE)
    _go_to_stats(page)

    text = page.locator('[data-testid="stats-using-since"]').text_content()
    assert '03/15/2026' in text, f"Expected English date format, got: {text}"


def test_stats_date_format_french(page: Page, app_url: str):
    """Date is formatted as DD/MM/YYYY in French."""
    _set_date_and_reload(page, app_url, _KNOWN_DATE)

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'en')
    page.wait_for_timeout(300)
    page.select_option('[data-testid="settings-language"]', 'fr')
    page.wait_for_timeout(500)

    _go_to_stats(page)

    text = page.locator('[data-testid="stats-using-since"]').text_content()
    assert '15/03/2026' in text, f"Expected French date format, got: {text}"

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'en')
    page.wait_for_timeout(300)


def test_stats_date_format_danish(page: Page, app_url: str):
    """Date is formatted as DD.MM.YYYY in Danish (not the US fallback)."""
    _set_date_and_reload(page, app_url, _KNOWN_DATE)

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'da')
    page.wait_for_timeout(500)

    _go_to_stats(page)

    text = page.locator('[data-testid="stats-using-since"]').text_content()
    assert '15.03.2026' in text, f"Expected Danish date format, got: {text}"

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'en')
    page.wait_for_timeout(300)


def test_stats_date_format_updates_on_language_change(page: Page, app_url: str):
    """Switching language updates the date format reactively without navigation."""
    _set_date_and_reload(page, app_url, _KNOWN_DATE)

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'en')
    page.wait_for_timeout(300)

    _go_to_stats(page)

    text = page.locator('[data-testid="stats-using-since"]').text_content()
    assert '03/15/2026' in text, f"Expected English date format, got: {text}"

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'fr')
    page.wait_for_timeout(500)

    _go_to_stats(page)

    text = page.locator('[data-testid="stats-using-since"]').text_content()
    assert '15/03/2026' in text, f"Expected French date format, got: {text}"

    page.click('a[href="#/settings"]')
    page.wait_for_url("**/#/settings", timeout=5000)
    page.wait_for_selector('[data-testid="settings-language"]')
    page.select_option('[data-testid="settings-language"]', 'en')
    page.wait_for_timeout(300)


# ── New Step 9 tests ─────────────────────────────────────────────────────────


def test_stats_date_range_tabs_visible(page: Page, app_url: str):
    """All four date range tabs are rendered on the stats page."""
    _wait_for_db(page, app_url)
    _go_to_stats(page)

    for key in ('7d', '30d', '3m', 'all'):
        tab = page.locator(f'[data-testid="date-range-{key}"]')
        expect(tab).to_be_visible()

    # 30d should be active by default
    active = page.locator('[data-testid="date-range-30d"]')
    assert 'active' in (active.get_attribute('class') or ''), "30d tab should be active by default"


def test_stats_summary_cards_zero_when_no_data(page: Page, app_url: str):
    """Summary cards show 0 when there are no execution logs."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _go_to_stats(page)

    sessions_card = page.locator('[data-testid="stats-total-sessions"] .stat-value')
    exercises_card = page.locator('[data-testid="stats-total-exercises"] .stat-value')
    streak_card = page.locator('[data-testid="stats-streak"] .stat-value')

    expect(sessions_card).to_have_text('0')
    expect(exercises_card).to_have_text('0')
    expect(streak_card).to_have_text('0')


def test_stats_summary_cards_with_data(page: Page, app_url: str):
    """Summary cards reflect correct counts after seeding two execution logs."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Stats Push-up')
    sess_id = _create_session(page, 'Stats Session')

    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), reps=10, completed=True)
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(2), reps=8, completed=True)

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

    # Click "All time" so both logs are counted regardless of default 30d window
    page.click('[data-testid="date-range-all"]')
    page.wait_for_timeout(500)

    sessions_val = page.locator('[data-testid="stats-total-sessions"] .stat-value').text_content()
    exercises_val = page.locator('[data-testid="stats-total-exercises"] .stat-value').text_content()

    assert int(sessions_val) >= 2, f"Expected ≥2 sessions, got {sessions_val}"
    assert int(exercises_val) >= 2, f"Expected ≥2 exercises done, got {exercises_val}"


def test_stats_date_range_filter(page: Page, app_url: str):
    """Switching to 7d hides logs older than 7 days; All time shows all."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Range Exercise')
    sess_id = _create_session(page, 'Range Session')

    # One log yesterday (within 7d), one log 20 days ago (outside 7d, inside 30d)
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), completed=True)
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(20), completed=True)

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

    # 30d should show 2
    page.click('[data-testid="date-range-30d"]')
    page.wait_for_timeout(500)
    val_30d = page.locator('[data-testid="stats-total-sessions"] .stat-value').text_content()
    assert int(val_30d) >= 2, f"Expected ≥2 sessions in 30d, got {val_30d}"

    # 7d should show 1
    page.click('[data-testid="date-range-7d"]')
    page.wait_for_timeout(500)
    val_7d = page.locator('[data-testid="stats-total-sessions"] .stat-value').text_content()
    assert int(val_7d) == 1, f"Expected 1 session in 7d, got {val_7d}"


def test_stats_frequency_no_data_message(page: Page, app_url: str):
    """Frequency section shows a no-data message when no sessions exist."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _go_to_stats(page)

    nodata = page.locator('[data-testid="stats-frequency-nodata"]')
    expect(nodata).to_be_visible()
    assert page.locator('[data-testid="stats-frequency-chart"]').count() == 0


def test_stats_volume_no_data_message(page: Page, app_url: str):
    """Volume section shows a no-data message when no sessions exist."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _go_to_stats(page)

    nodata = page.locator('[data-testid="stats-volume-nodata"]')
    expect(nodata).to_be_visible()
    assert page.locator('[data-testid="stats-volume-chart"]').count() == 0


def test_stats_progression_no_data_message(page: Page, app_url: str):
    """Progression section shows select-exercise prompt before an exercise is picked."""
    _wait_for_db(page, app_url)
    _go_to_stats(page)

    nodata = page.locator('[data-testid="stats-progression-nodata"]')
    expect(nodata).to_be_visible()
    assert page.locator('[data-testid="stats-progression-chart"]').count() == 0


def test_stats_frequency_chart_renders(page: Page, app_url: str):
    """Frequency chart canvas appears after seeding sessions in current period."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Freq Exercise')
    sess_id = _create_session(page, 'Freq Session')
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), completed=True)

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

    page.click('[data-testid="date-range-all"]')
    page.wait_for_timeout(500)

    chart = page.locator('[data-testid="stats-frequency-chart"]')
    expect(chart).to_be_visible()
    canvas = chart.locator('canvas')
    expect(canvas).to_be_visible()


def test_stats_volume_chart_renders(page: Page, app_url: str):
    """Volume chart appears when sessions have weight data."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Vol Exercise')
    sess_id = _create_session(page, 'Vol Session')
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), reps=10, weight=50.0, completed=True)

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

    page.click('[data-testid="date-range-all"]')
    page.wait_for_timeout(500)

    chart = page.locator('[data-testid="stats-volume-chart"]')
    expect(chart).to_be_visible()
    canvas = chart.locator('canvas')
    expect(canvas).to_be_visible()


def test_stats_progression_chart_renders(page: Page, app_url: str):
    """Progression chart renders after selecting an exercise from the dropdown."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Prog Exercise')
    sess_id = _create_session(page, 'Prog Session')
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), reps=12, weight=60.0, completed=True)

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

    page.click('[data-testid="date-range-all"]')
    page.wait_for_timeout(500)

    # The dropdown should have the exercise as an option
    select = page.locator('[data-testid="stats-progression-exercise"]')
    expect(select).to_be_visible()
    select.select_option(label='Prog Exercise')
    page.wait_for_timeout(500)

    chart = page.locator('[data-testid="stats-progression-chart"]')
    expect(chart).to_be_visible()
    canvas = chart.locator('canvas')
    expect(canvas).to_be_visible()


def test_stats_streak_consecutive_days(page: Page, app_url: str):
    """Streak counter reflects consecutive days with at least one session."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Streak Exercise')
    sess_id = _create_session(page, 'Streak Session')

    # Three consecutive days ending today
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(0), completed=True)
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), completed=True)
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(2), completed=True)

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

    streak_val = page.locator('[data-testid="stats-streak"] .stat-value').text_content()
    assert int(streak_val) >= 3, f"Expected streak ≥ 3, got {streak_val}"


def test_stats_streak_ignores_aborted_sessions(page: Page, app_url: str):
    """Aborted sessions do not extend the streak: one completed log today plus
    one aborted log yesterday must give a streak of 1, not 2."""
    _wait_for_db(page, app_url)
    _delete_all_logs(page)
    _delete_all_exercises(page)
    _delete_all_sessions(page)

    ex_id = _create_exercise(page, 'Aborted Streak Exercise')
    sess_id = _create_session(page, 'Aborted Streak Session')

    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(0), status='completed')
    _create_log_with_items(page, sess_id, ex_id, _iso_days_ago(1), status='aborted')

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

    streak_val = page.locator('[data-testid="stats-streak"] .stat-value').text_content()
    assert int(streak_val) == 1, f"Expected streak 1 (aborted must not count), got {streak_val}"
