"""Tests du client licence : session HTTP injectée, aucun appel réseau réel.

Le client accepte une session mockable. On vérifie : activation réussie +
stockage local, refus serveur (4xx), serveur indisponible (exception réseau),
réponse illisible, check, et lecture du statut local.
"""

from __future__ import annotations

import pytest

from gui_v6.license_client import LicenseClient, LicenseStatus
from gui_v6.license_store import LicenseStore


class FakeResponse:
    def __init__(self, status_code, payload=None, raise_on_json=False):
        self.status_code = status_code
        self._payload = payload
        self._raise_on_json = raise_on_json

    def json(self):
        if self._raise_on_json:
            raise ValueError("not json")
        return self._payload


class FakeSession:
    """Session HTTP mockable : enregistre les appels, renvoie des réponses scriptées."""

    def __init__(self, response=None, exc=None):
        self._response = response
        self._exc = exc
        self.calls = []

    def post(self, url, json, timeout):
        self.calls.append({"url": url, "json": json, "timeout": timeout})
        if self._exc is not None:
            raise self._exc
        return self._response


def _client(tmp_path, session):
    store = LicenseStore(tmp_path / "license.json")
    return LicenseClient("https://portail.example/", session=session, store=store), store


def test_activate_success_stores_license(tmp_path):
    payload = {
        "state": "active",
        "license": {
            "payload": {
                "license_ref": "LIC-9",
                "expires_at": "2027-06-01",
                "grace_days": 7,
                "machine_id": "MID-0001",
            },
            "signature": "signed",
            "alg": "RSASSA-PSS-SHA256",
        },
    }
    session = FakeSession(FakeResponse(200, payload))
    client, store = _client(tmp_path, session)

    status = client.activate("TOKEN-ABC", "MID-0001")

    assert isinstance(status, LicenseStatus)
    assert status.valid is True
    assert status.status == "active"
    assert status.license_ref == "LIC-9"
    assert status.grace_days == 7
    # La licence signée est stockée localement.
    assert store.load() == payload
    # L'URL et le payload envoyés sont corrects.
    assert session.calls[0]["url"] == "https://portail.example/api/v1/activate"
    assert session.calls[0]["json"] == {"token": "TOKEN-ABC", "machine_id": "MID-0001"}


def test_activate_grace_is_valid(tmp_path):
    session = FakeSession(
        FakeResponse(200, {"state": "grace", "license": {"payload": {"grace_days": 3}}})
    )
    client, _ = _client(tmp_path, session)
    status = client.activate("T", "M")
    assert status.valid is True
    assert status.status == "grace"


def test_activate_rejected_4xx_is_invalid_and_not_stored(tmp_path):
    session = FakeSession(FakeResponse(403, {"detail": "token revoked"}))
    client, store = _client(tmp_path, session)

    status = client.activate("BAD", "MID-1")

    assert status.valid is False
    assert status.status == "invalid"
    assert "revoked" in status.message
    # Rien n'est stocké en cas de refus.
    assert store.load() is None


def test_activate_server_unavailable_does_not_crash(tmp_path):
    session = FakeSession(exc=ConnectionError("server down"))
    client, store = _client(tmp_path, session)

    status = client.activate("T", "M")

    assert status.valid is False
    assert status.status == "unavailable"
    assert store.load() is None


def test_activate_unreadable_response_is_unavailable(tmp_path):
    session = FakeSession(FakeResponse(200, raise_on_json=True))
    client, _ = _client(tmp_path, session)
    status = client.activate("T", "M")
    assert status.status == "unavailable"


def test_check_active(tmp_path):
    payload = {"state": "active", "license": {"payload": {"license_ref": "LIC-1"}}}
    session = FakeSession(FakeResponse(200, payload))
    client, store = _client(tmp_path, session)

    status = client.check("LIC-1", "MID-1")

    assert status.valid is True
    assert session.calls[0]["url"] == "https://portail.example/api/v1/check"
    assert session.calls[0]["json"] == {"license_ref": "LIC-1", "machine_id": "MID-1"}
    assert store.load() == payload


def test_check_expired_is_not_valid(tmp_path):
    session = FakeSession(FakeResponse(200, {"state": "expired"}))
    client, _ = _client(tmp_path, session)
    status = client.check("LIC-1", "MID-1")
    assert status.valid is False
    assert status.status == "expired"


def test_check_revoked_without_license_payload(tmp_path):
    session = FakeSession(FakeResponse(200, {"state": "revoked"}))
    client, store = _client(tmp_path, session)

    status = client.check("LIC-1", "MID-1")

    assert status.valid is False
    assert status.status == "revoked"
    assert store.load() is None


def test_check_server_unavailable(tmp_path):
    session = FakeSession(exc=TimeoutError("timeout"))
    client, _ = _client(tmp_path, session)
    status = client.check("LIC-1", "MID-1")
    assert status.status == "unavailable"


def test_local_status_none_when_empty(tmp_path):
    session = FakeSession(FakeResponse(200, {}))
    client, _ = _client(tmp_path, session)
    status = client.local_status()
    assert status.valid is False
    assert status.status == "none"


def test_local_status_reads_store(tmp_path):
    store = LicenseStore(tmp_path / "license.json")
    store.save({"state": "active", "license": {"payload": {"license_ref": "LIC-7"}}})
    client = LicenseClient("https://x", session=FakeSession(), store=store)
    status = client.local_status()
    assert status.valid is True
    assert status.license_ref == "LIC-7"


def test_status_never_exposes_token():
    # Le statut ne porte pas de token : la repr ne peut pas le fuiter.
    status = LicenseStatus.from_payload({"status": "active", "license_ref": "LIC-1"})
    assert "token" not in repr(status).lower()
