"""Tests for cloud authentication module."""

import json
import os
import tempfile
import time
import unittest
from pathlib import Path
from unittest.mock import patch

from rcl_rlm.cloud_auth import (
    CREDENTIALS_FILE,
    CloudAuthError,
    CloudCredentials,
    WrapperSession,
    clear_credentials,
    get_cloud_url,
    load_credentials,
    save_credentials,
)


class TestCloudCredentials(unittest.TestCase):
    """Tests for CloudCredentials dataclass."""

    def test_credentials_not_expired_when_no_expiry(self):
        """Credentials without expiry should not be expired."""
        creds = CloudCredentials(session_token="test-token")
        self.assertFalse(creds.is_expired())

    def test_credentials_not_expired_when_future(self):
        """Credentials with future expiry should not be expired."""
        future = time.time() + 3600  # 1 hour from now
        creds = CloudCredentials(session_token="test-token", expires_at=future)
        self.assertFalse(creds.is_expired())

    def test_credentials_expired_when_past(self):
        """Credentials with past expiry should be expired."""
        past = time.time() - 3600  # 1 hour ago
        creds = CloudCredentials(session_token="test-token", expires_at=past)
        self.assertTrue(creds.is_expired())

    def test_credentials_expired_with_buffer(self):
        """Credentials should be considered expired 5 minutes before actual expiry."""
        # Expires in 4 minutes (less than 5 minute buffer)
        almost_expired = time.time() + 240
        creds = CloudCredentials(session_token="test-token", expires_at=almost_expired)
        self.assertTrue(creds.is_expired())

    def test_credentials_to_dict_and_from_dict(self):
        """Credentials should roundtrip through dict serialization."""
        original = CloudCredentials(
            session_token="test-token",
            expires_at=1234567890.0,
            refresh_token="refresh-token",
            refresh_expires_at=1234569999.0,
        )
        data = original.to_dict()
        restored = CloudCredentials.from_dict(data)
        self.assertEqual(original.session_token, restored.session_token)
        self.assertEqual(original.expires_at, restored.expires_at)
        self.assertEqual(original.refresh_token, restored.refresh_token)
        self.assertEqual(original.refresh_expires_at, restored.refresh_expires_at)

    def test_refresh_token_expired_when_missing(self):
        """Refresh token should be treated as expired when missing."""
        creds = CloudCredentials(session_token="test-token")
        self.assertTrue(creds.refresh_is_expired())

    def test_refresh_token_not_expired_when_future(self):
        """Refresh token with future expiry should not be expired."""
        future = time.time() + 3600
        creds = CloudCredentials(session_token="test-token", refresh_expires_at=future)
        self.assertFalse(creds.refresh_is_expired())


class TestWrapperSession(unittest.TestCase):
    """Tests for WrapperSession dataclass."""

    def test_session_not_expired_when_future(self):
        """Session with future expiry should not be expired."""
        future = time.time() + 3600
        session = WrapperSession(token="test-token", expires_at=future)
        self.assertFalse(session.is_expired())

    def test_session_expired_when_past(self):
        """Session with past expiry should be expired."""
        past = time.time() - 3600
        session = WrapperSession(token="test-token", expires_at=past)
        self.assertTrue(session.is_expired())

    def test_session_expired_with_buffer(self):
        """Session should be considered expired 30 seconds before actual expiry."""
        almost_expired = time.time() + 20  # 20 seconds (less than 30 second buffer)
        session = WrapperSession(token="test-token", expires_at=almost_expired)
        self.assertTrue(session.is_expired())


class TestCredentialStorage(unittest.TestCase):
    """Tests for credential storage functions."""

    def setUp(self):
        """Set up test with temporary credentials file."""
        self.temp_dir = tempfile.mkdtemp()
        self.temp_creds_file = Path(self.temp_dir) / "credentials.json"
        self.patcher = patch("rcl_rlm.cloud_auth.CREDENTIALS_FILE", self.temp_creds_file)
        self.patcher.start()

    def tearDown(self):
        """Clean up temporary files."""
        self.patcher.stop()
        if self.temp_creds_file.exists():
            self.temp_creds_file.unlink()
        Path(self.temp_dir).rmdir()

    def test_load_returns_none_when_no_file(self):
        """load_credentials should return None when file doesn't exist."""
        creds = load_credentials()
        self.assertIsNone(creds)

    def test_save_and_load_roundtrip(self):
        """Credentials should roundtrip through save/load."""
        original = CloudCredentials(session_token="test-token", expires_at=time.time() + 3600)
        save_credentials(original)
        loaded = load_credentials()
        self.assertIsNotNone(loaded)
        self.assertEqual(original.session_token, loaded.session_token)

    def test_load_returns_none_for_expired_credentials(self):
        """load_credentials should return None for expired credentials."""
        expired = CloudCredentials(session_token="test-token", expires_at=time.time() - 3600)
        save_credentials(expired)
        loaded = load_credentials()
        self.assertIsNone(loaded)

    def test_clear_credentials(self):
        """clear_credentials should remove the credentials file."""
        creds = CloudCredentials(session_token="test-token")
        save_credentials(creds)
        self.assertTrue(self.temp_creds_file.exists())
        clear_credentials()
        self.assertFalse(self.temp_creds_file.exists())

    def test_clear_credentials_no_op_when_no_file(self):
        """clear_credentials should not raise when file doesn't exist."""
        clear_credentials()  # Should not raise


class TestEnvironmentOverrides(unittest.TestCase):
    """Tests for environment variable overrides."""

    def setUp(self):
        """Store original environment."""
        self.original_env = os.environ.copy()

    def tearDown(self):
        """Restore original environment."""
        os.environ.clear()
        os.environ.update(self.original_env)

    def test_load_credentials_from_env(self):
        """load_credentials should use RECALL_CLOUD_SESSION if set."""
        os.environ["RECALL_CLOUD_SESSION"] = "env-token"
        creds = load_credentials()
        self.assertIsNotNone(creds)
        self.assertEqual("env-token", creds.session_token)

    def test_get_cloud_url_default(self):
        """get_cloud_url should return default when env not set."""
        os.environ.pop("RECALL_CLOUD_URL", None)
        url = get_cloud_url()
        self.assertEqual("https://recall-ai.fly.dev", url)

    def test_get_cloud_url_from_env(self):
        """get_cloud_url should use RECALL_CLOUD_URL if set."""
        os.environ["RECALL_CLOUD_URL"] = "https://custom.example.com"
        url = get_cloud_url()
        self.assertEqual("https://custom.example.com", url)


if __name__ == "__main__":
    unittest.main()
