"""Tests for the OfflineValidator using real Ed25519 keypairs."""

from __future__ import annotations

import base64
import json
import time

import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
    Ed25519PrivateKey,
    Ed25519PublicKey,
)

from licensesos.offline_validator import OfflineValidator
from licensesos.results.offline_validation import OfflineValidationResult


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def generate_test_keypair() -> tuple[str, Ed25519PrivateKey]:
    """Generate an Ed25519 keypair and return (public_key_base64, private_key)."""
    private_key = Ed25519PrivateKey.generate()
    public_key = private_key.public_key()
    raw_public_bytes = public_key.public_bytes_raw()
    public_key_base64 = base64.b64encode(raw_public_bytes).decode("ascii")
    return public_key_base64, private_key


def base64url_encode(data: bytes) -> str:
    """Base64url-encode without padding."""
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")


def create_token(payload: dict, private_key: Ed25519PrivateKey) -> str:
    """Create a signed offline token from a payload."""
    payload_json = json.dumps(payload).encode("utf-8")
    payload_b64 = base64url_encode(payload_json)
    signature = private_key.sign(payload_json)
    signature_b64 = base64url_encode(signature)
    return f"{payload_b64}.{signature_b64}"


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


class TestOfflineValidator:
    @pytest.fixture(autouse=True)
    def setup_keys(self) -> None:
        self.public_key_b64, self.private_key = generate_test_keypair()

    def _valid_payload(self) -> dict:
        return {
            "license_id": "lic_test_123",
            "status": "active",
            "entitlements": {"pro": True},
            "expires_at": "2025-12-31T00:00:00+00:00",
            "activation_limit": 5,
            "activation_count": 1,
            "identifier": "example.com",
            "activated": True,
            "uses_count": 10,
            "max_uses": 100,
            "iat": int(time.time()) - 60,
            "exp": int(time.time()) + 3600,
            "ttl": 3660,
        }

    def test_validates_correctly_signed_token(self) -> None:
        validator = OfflineValidator(self.public_key_b64)
        token = create_token(self._valid_payload(), self.private_key)

        result = validator.validate(token)

        assert isinstance(result, OfflineValidationResult)
        assert result.is_valid() is True
        assert result.is_expired() is False
        assert result.license_id == "lic_test_123"
        assert result.status == "active"
        assert result.entitlements == {"pro": True}
        assert result.activation_limit == 5
        assert result.activation_count == 1
        assert result.identifier == "example.com"
        assert result.activated is True
        assert result.uses_count == 10
        assert result.max_uses == 100
        assert result.remaining_seconds() > 3500

    def test_rejects_expired_token(self) -> None:
        validator = OfflineValidator(self.public_key_b64)
        payload = self._valid_payload()
        payload["exp"] = int(time.time()) - 60  # expired 60s ago
        token = create_token(payload, self.private_key)

        result = validator.validate(token)

        assert result.is_valid() is False
        assert result.error_code == "TOKEN_EXPIRED"
        assert result.is_expired() is True
        assert result.remaining_seconds() == 0
        # Payload data should still be accessible
        assert result.license_id == "lic_test_123"
        assert result.status == "active"

    def test_rejects_invalid_signature(self) -> None:
        # Use a different keypair to sign
        _, wrong_key = generate_test_keypair()
        validator = OfflineValidator(self.public_key_b64)
        token = create_token(self._valid_payload(), wrong_key)

        result = validator.validate(token)

        assert result.is_valid() is False
        assert result.error_code == "INVALID_SIGNATURE"

    def test_rejects_malformed_token_no_dot(self) -> None:
        validator = OfflineValidator(self.public_key_b64)
        result = validator.validate("not-a-valid-token")

        assert result.is_valid() is False
        assert result.error_code == "INVALID_TOKEN_FORMAT"

    def test_rejects_token_with_too_many_parts(self) -> None:
        validator = OfflineValidator(self.public_key_b64)
        result = validator.validate("a.b.c")

        assert result.is_valid() is False
        assert result.error_code == "INVALID_TOKEN_FORMAT"

    def test_rejects_tampered_payload(self) -> None:
        validator = OfflineValidator(self.public_key_b64)
        token = create_token(self._valid_payload(), self.private_key)
        parts = token.split(".")

        # Tamper with the payload
        tampered = {**self._valid_payload(), "status": "premium"}
        tampered_b64 = base64url_encode(json.dumps(tampered).encode("utf-8"))
        tampered_token = f"{tampered_b64}.{parts[1]}"

        result = validator.validate(tampered_token)

        assert result.is_valid() is False
        assert result.error_code == "INVALID_SIGNATURE"

    def test_handles_minimal_payload(self) -> None:
        validator = OfflineValidator(self.public_key_b64)
        minimal_payload = {
            "license_id": "lic_min",
            "status": "active",
            "exp": int(time.time()) + 3600,
            "iat": int(time.time()),
            "ttl": 3600,
        }
        token = create_token(minimal_payload, self.private_key)

        result = validator.validate(token)

        assert result.is_valid() is True
        assert result.license_id == "lic_min"
        assert result.entitlements is None
        assert result.activation_limit is None
        assert result.activation_count == 0
