"""Teste keepalive RAR — login de proba periodic ca dashboard-ul sa nu afiseze
fals "RAR inaccesibil" doar din lipsa de trafic.

Comportament asteptat (_maybe_keepalive):
 - login vechi/lipsa + creds durabile -> sondeaza (get_token apelat) si forteaza
   login real (invalidate inainte);
 - login proaspat (sub interval) -> NU sondeaza;
 - interval=0 -> dezactivat;
 - fara cont cu creds durabile -> nu sondeaza;
 - gating: dupa o incercare, nu re-sondeaza in cadrul intervalului (nu hartui RAR).
"""

from __future__ import annotations

import os
import tempfile
from datetime import datetime, timedelta, timezone

import pytest


@pytest.fixture()
def env(monkeypatch):
    tmp = tempfile.mkdtemp()
    monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
    from app.config import get_settings
    get_settings.cache_clear()
    from app.db import get_connection, init_db
    init_db()
    conn = get_connection()
    yield conn, get_settings()
    conn.close()
    get_settings.cache_clear()


class _FakeSessions:
    """Imita AccountSessions: get_token reusit reimprospateaza heartbeat-ul (ca realul)."""

    def __init__(self, conn, *, fail: bool = False):
        self._conn = conn
        self._fail = fail
        self.invalidated: list[int] = []
        self.tokens: list[int] = []

    def invalidate(self, account_id: int, rar_env=None) -> None:
        self.invalidated.append(account_id)

    def get_token(self, conn, account_id: int, creds, rar_env="test") -> str | None:
        self.tokens.append(account_id)
        if self._fail:
            raise RuntimeError("RAR jos")
        from app.db import write_heartbeat
        write_heartbeat(conn, rar_login_ok=True, detail=f"login proba (cont {account_id})")
        return "tok"


def _set_last_login(conn, *, ago_s: float | None):
    """Seteaza last_rar_login_ok la now-ago_s (None = niciun login)."""
    from app.db import write_heartbeat
    write_heartbeat(conn, detail="poll")  # asigura randul heartbeat
    if ago_s is None:
        conn.execute("UPDATE worker_heartbeat SET last_rar_login_ok=NULL WHERE id=1")
    else:
        ts = (datetime.now(timezone.utc) - timedelta(seconds=ago_s)).isoformat()
        conn.execute("UPDATE worker_heartbeat SET last_rar_login_ok=? WHERE id=1", (ts,))
    conn.commit()


def _account_cu_creds(conn) -> int:
    """Creeaza cont cu creds in slotul per-env (US-013 — legacy rar_creds_enc dropata)."""
    from app.accounts import create_account
    from app.crypto import encrypt_creds
    acct = create_account(conn, "Service Cu Creds", email="svc@example.com")
    enc = encrypt_creds({"email": "svc@example.com", "password": "secret"})
    # US-013: scrie in slotul per-env; rar_env din fixture = valoarea default (test sau prod).
    # Folosim rar_creds_test_enc si rar_creds_prod_enc (ambele) pentru robustete.
    conn.execute(
        "UPDATE accounts SET rar_creds_test_enc=?, rar_test_enabled=1, "
        "rar_creds_prod_enc=?, rar_prod_enabled=1 WHERE id=?",
        (enc, enc, acct),
    )
    conn.commit()
    return acct


def test_login_vechi_sondeaza_si_reimprospateaza(env):
    """Login mai vechi decat intervalul + creds durabile -> proba reala, heartbeat reimprospatat."""
    from app.worker.__main__ import _maybe_keepalive
    from app.db import read_heartbeat

    conn, settings = env
    settings.worker_rar_keepalive_interval_s = 86400
    acct = _account_cu_creds(conn)
    _set_last_login(conn, ago_s=100000)  # > 24h

    sessions = _FakeSessions(conn)
    _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0})

    assert sessions.tokens == [acct]          # a sondat contul cu creds
    assert sessions.invalidated == [acct]     # a fortat login real (nu token din cache)
    last = read_heartbeat(conn)["last_rar_login_ok"]
    age = (datetime.now(timezone.utc) - datetime.fromisoformat(last)).total_seconds()
    assert age < 60                           # heartbeat reimprospatat de proba


def test_login_proaspat_nu_sondeaza(env):
    """Login sub interval -> niciun login de proba."""
    from app.worker.__main__ import _maybe_keepalive

    conn, settings = env
    settings.worker_rar_keepalive_interval_s = 86400
    _account_cu_creds(conn)
    _set_last_login(conn, ago_s=3600)  # 1h < 24h

    sessions = _FakeSessions(conn)
    _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0})

    assert sessions.tokens == []


def test_interval_zero_dezactivat(env):
    """interval=0 -> keepalive dezactivat, nicio proba chiar cu login vechi."""
    from app.worker.__main__ import _maybe_keepalive

    conn, settings = env
    settings.worker_rar_keepalive_interval_s = 0
    _account_cu_creds(conn)
    _set_last_login(conn, ago_s=100000)

    sessions = _FakeSessions(conn)
    _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0})

    assert sessions.tokens == []


def test_fara_creds_durabile_nu_sondeaza(env):
    """Niciun cont cu creds durabile + fara test-creds -> nimic de sondat."""
    from app.worker.__main__ import _maybe_keepalive

    conn, settings = env
    settings.worker_rar_keepalive_interval_s = 86400
    settings.worker_use_test_creds = False
    _set_last_login(conn, ago_s=100000)

    sessions = _FakeSessions(conn)
    _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0})

    assert sessions.tokens == []


def test_target_sare_creds_nedecriptabile(env):
    """Cont cu creds criptate sub alta cheie (decrypt -> None) e sarit; alege contul valid.

    Reproduce bug-ul real: start.sh both genereaza o cheie efemera noua la fiecare
    pornire, deci creds-urile durabile vechi nu se mai decripteaza.
    """
    from app.worker.__main__ import _keepalive_target
    from app.accounts import create_account
    from app.crypto import encrypt_creds

    conn, settings = env
    settings.worker_use_test_creds = False
    # Cont cu creds GUNOI (nedecriptabile sub cheia curenta), id mai mic.
    bad = create_account(conn, "Cont Cheie Veche", email="old@example.com")
    # US-013: scrie in slotul per-env (rar_env = settings.rar_env, implicit in fixture)
    bad_slot = f"rar_creds_{settings.rar_env}_enc"
    conn.execute(f"UPDATE accounts SET {bad_slot}='gAAAAA-token-invalid' WHERE id=?", (bad,))
    # Cont cu creds valide, id mai mare.
    good = create_account(conn, "Cont Valid", email="good@example.com")
    enc = encrypt_creds({"email": "good@example.com", "password": "pw"})
    good_slot = f"rar_creds_{settings.rar_env}_enc"
    conn.execute(f"UPDATE accounts SET {good_slot}=? WHERE id=?", (enc, good))
    conn.commit()

    acct_id, creds = _keepalive_target(conn, settings)
    assert acct_id == good                       # a sarit contul nedecriptabil
    assert creds and creds["email"] == "good@example.com"


def test_gating_nu_hartuieste_pe_esec(env):
    """Pe esec (RAR jos) login-ul ramane vechi; a doua trecere imediata NU re-sondeaza."""
    from app.worker.__main__ import _maybe_keepalive

    conn, settings = env
    settings.worker_rar_keepalive_interval_s = 86400
    _account_cu_creds(conn)
    _set_last_login(conn, ago_s=100000)

    state = {"last_attempt": 0.0}
    sessions = _FakeSessions(conn, fail=True)
    _maybe_keepalive(conn, settings, sessions, state)   # incearca, esueaza
    _maybe_keepalive(conn, settings, sessions, state)   # gating: nu re-incearca

    assert sessions.tokens == [sessions.invalidated[0]]  # o singura proba
    assert len(sessions.tokens) == 1
