"""Tests for authentication models."""

from datetime import datetime, timedelta

import pytest

from platform_2step_mcp.auth.models import (
    DeviceAuthResponse,
    TokenData,
    TokenResponse,
)


class TestDeviceAuthResponse:
    """Tests for DeviceAuthResponse model."""

    def test_parse_full_response(self):
        """Test parsing a complete device auth response."""
        data = {
            "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
            "user_code": "WDJB-MJHT",
            "verification_uri": "https://auth.agendapro.com/device",
            "verification_uri_complete": "https://auth.agendapro.com/device?user_code=WDJB-MJHT",
            "expires_in": 1800,
            "interval": 5,
        }
        response = DeviceAuthResponse(**data)

        assert response.device_code == data["device_code"]
        assert response.user_code == data["user_code"]
        assert response.verification_uri == data["verification_uri"]
        assert response.verification_uri_complete == data["verification_uri_complete"]
        assert response.expires_in == 1800
        assert response.interval == 5

    def test_parse_minimal_response(self):
        """Test parsing a minimal device auth response."""
        data = {
            "device_code": "abc123",
            "user_code": "XYZ-123",
            "verification_uri": "https://auth.example.com/device",
            "expires_in": 600,
        }
        response = DeviceAuthResponse(**data)

        assert response.device_code == "abc123"
        assert response.user_code == "XYZ-123"
        assert response.verification_uri_complete is None
        assert response.interval == 5  # Default value


class TestTokenResponse:
    """Tests for TokenResponse model."""

    def test_parse_cognito_response(self):
        """Test parsing a Cognito token response (with id_token, no scope)."""
        data = {
            "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
            "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
            "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
            "token_type": "Bearer",
            "expires_in": 3600,
        }
        response = TokenResponse(**data)

        assert response.access_token == data["access_token"]
        assert response.id_token == data["id_token"]
        assert response.refresh_token == data["refresh_token"]
        assert response.token_type == "Bearer"
        assert response.expires_in == 3600
        assert response.scope is None

    def test_parse_legacy_response_with_scope(self):
        """Test parsing a legacy token response (with scope, no id_token)."""
        data = {
            "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
            "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
            "token_type": "Bearer",
            "expires_in": 3600,
            "scope": "read create_pending",
        }
        response = TokenResponse(**data)

        assert response.access_token == data["access_token"]
        assert response.id_token is None
        assert response.refresh_token == data["refresh_token"]
        assert response.token_type == "Bearer"
        assert response.expires_in == 3600
        assert response.scope == "read create_pending"

    def test_parse_minimal_response(self):
        """Test parsing a minimal token response."""
        data = {
            "access_token": "test-token",
            "expires_in": 1800,
        }
        response = TokenResponse(**data)

        assert response.access_token == "test-token"
        assert response.id_token is None
        assert response.refresh_token is None
        assert response.token_type == "Bearer"  # Default
        assert response.scope is None  # No default for Cognito compatibility


class TestTokenData:
    """Tests for TokenData dataclass."""

    def test_create_token_data_cognito(self):
        """Test creating TokenData with Cognito tokens (id_token, no scope)."""
        expires_at = datetime.utcnow() + timedelta(hours=1)
        token = TokenData(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_at=expires_at,
            id_token="test-id-token",
        )

        assert token.access_token == "test-token"
        assert token.refresh_token == "test-refresh"
        assert token.expires_at == expires_at
        assert token.id_token == "test-id-token"
        assert token.scope is None

    def test_create_token_data_legacy(self):
        """Test creating TokenData with legacy tokens (scope, no id_token)."""
        expires_at = datetime.utcnow() + timedelta(hours=1)
        token = TokenData(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_at=expires_at,
            scope="read create_pending",
        )

        assert token.access_token == "test-token"
        assert token.refresh_token == "test-refresh"
        assert token.expires_at == expires_at
        assert token.id_token is None
        assert token.scope == "read create_pending"

    def test_is_expired_false(self):
        """Test is_expired returns False for valid token."""
        token = TokenData(
            access_token="test",
            refresh_token=None,
            expires_at=datetime.utcnow() + timedelta(hours=1),
        )
        assert not token.is_expired()

    def test_is_expired_true(self):
        """Test is_expired returns True for expired token."""
        token = TokenData(
            access_token="test",
            refresh_token=None,
            expires_at=datetime.utcnow() - timedelta(hours=1),
        )
        assert token.is_expired()

    def test_is_expiring_soon_true(self):
        """Test is_expiring_soon returns True when expiring within minutes."""
        token = TokenData(
            access_token="test",
            refresh_token=None,
            expires_at=datetime.utcnow() + timedelta(minutes=3),
        )
        assert token.is_expiring_soon(minutes=5)

    def test_is_expiring_soon_false(self):
        """Test is_expiring_soon returns False when not expiring soon."""
        token = TokenData(
            access_token="test",
            refresh_token=None,
            expires_at=datetime.utcnow() + timedelta(hours=1),
        )
        assert not token.is_expiring_soon(minutes=5)

    def test_to_dict_cognito(self):
        """Test converting TokenData to dict with Cognito tokens."""
        expires_at = datetime(2024, 1, 15, 10, 0, 0)
        token = TokenData(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_at=expires_at,
            id_token="test-id-token",
        )

        data = token.to_dict()

        assert data["access_token"] == "test-token"
        assert data["refresh_token"] == "test-refresh"
        assert data["expires_at"] == "2024-01-15T10:00:00"
        assert data["id_token"] == "test-id-token"
        assert "scope" not in data  # Not included when None

    def test_to_dict_legacy(self):
        """Test converting TokenData to dict with legacy tokens."""
        expires_at = datetime(2024, 1, 15, 10, 0, 0)
        token = TokenData(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_at=expires_at,
            scope="read create_pending",
        )

        data = token.to_dict()

        assert data["access_token"] == "test-token"
        assert data["refresh_token"] == "test-refresh"
        assert data["expires_at"] == "2024-01-15T10:00:00"
        assert data["scope"] == "read create_pending"
        assert "id_token" not in data  # Not included when None

    def test_from_dict_cognito(self):
        """Test creating TokenData from dict with Cognito tokens."""
        data = {
            "access_token": "test-token",
            "refresh_token": "test-refresh",
            "expires_at": "2024-01-15T10:00:00",
            "id_token": "test-id-token",
        }

        token = TokenData.from_dict(data)

        assert token.access_token == "test-token"
        assert token.refresh_token == "test-refresh"
        assert token.expires_at == datetime(2024, 1, 15, 10, 0, 0)
        assert token.id_token == "test-id-token"
        assert token.scope is None

    def test_from_dict_legacy(self):
        """Test creating TokenData from dict with legacy tokens."""
        data = {
            "access_token": "test-token",
            "refresh_token": "test-refresh",
            "expires_at": "2024-01-15T10:00:00",
            "scope": "read create_pending",
        }

        token = TokenData.from_dict(data)

        assert token.access_token == "test-token"
        assert token.refresh_token == "test-refresh"
        assert token.expires_at == datetime(2024, 1, 15, 10, 0, 0)
        assert token.id_token is None
        assert token.scope == "read create_pending"

    def test_from_dict_minimal(self):
        """Test creating TokenData from minimal dict."""
        data = {
            "access_token": "test-token",
            "expires_at": "2024-01-15T10:00:00",
        }

        token = TokenData.from_dict(data)

        assert token.access_token == "test-token"
        assert token.refresh_token is None
        assert token.id_token is None
        assert token.scope is None

    def test_from_token_response_cognito(self):
        """Test creating TokenData from Cognito TokenResponse."""
        response = TokenResponse(
            access_token="test-token",
            id_token="test-id-token",
            refresh_token="test-refresh",
            expires_in=3600,
        )

        token = TokenData.from_token_response(response)

        assert token.access_token == "test-token"
        assert token.id_token == "test-id-token"
        assert token.refresh_token == "test-refresh"
        assert token.scope is None
        # Check that expires_at is approximately 1 hour from now
        expected_expiry = datetime.utcnow() + timedelta(seconds=3600)
        assert abs((token.expires_at - expected_expiry).total_seconds()) < 2

    def test_from_token_response_legacy(self):
        """Test creating TokenData from legacy TokenResponse."""
        response = TokenResponse(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_in=3600,
            scope="read",
        )

        token = TokenData.from_token_response(response)

        assert token.access_token == "test-token"
        assert token.id_token is None
        assert token.refresh_token == "test-refresh"
        assert token.scope == "read"
        expected_expiry = datetime.utcnow() + timedelta(seconds=3600)
        assert abs((token.expires_at - expected_expiry).total_seconds()) < 2

    def test_roundtrip_dict_cognito(self):
        """Test that to_dict and from_dict are inverses for Cognito tokens."""
        original = TokenData(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_at=datetime(2024, 1, 15, 10, 0, 0),
            id_token="test-id-token",
        )

        restored = TokenData.from_dict(original.to_dict())

        assert restored.access_token == original.access_token
        assert restored.refresh_token == original.refresh_token
        assert restored.expires_at == original.expires_at
        assert restored.id_token == original.id_token
        assert restored.scope == original.scope

    def test_roundtrip_dict_legacy(self):
        """Test that to_dict and from_dict are inverses for legacy tokens."""
        original = TokenData(
            access_token="test-token",
            refresh_token="test-refresh",
            expires_at=datetime(2024, 1, 15, 10, 0, 0),
            scope="read create_pending",
        )

        restored = TokenData.from_dict(original.to_dict())

        assert restored.access_token == original.access_token
        assert restored.refresh_token == original.refresh_token
        assert restored.expires_at == original.expires_at
        assert restored.id_token == original.id_token
        assert restored.scope == original.scope
