"""Tests for quota enforcement system."""

import json
from datetime import date
from unittest.mock import patch

import pytest

from deepsweep.quota import QuotaEnforcer, QuotaStatus, check_quota, record_validation


@pytest.fixture
def quota_enforcer(tmp_path, monkeypatch):
    """Create quota enforcer with temporary config directory."""
    config_dir = tmp_path / "deepsweep"
    monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
    # Ensure quota tests actually test quota logic (not bypassed)
    monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
    return QuotaEnforcer(offline_mode=True)


@pytest.fixture
def quota_enforcer_online(tmp_path, monkeypatch):
    """Create quota enforcer with temporary config directory (online mode)."""
    config_dir = tmp_path / "deepsweep"
    monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
    # Ensure quota tests actually test quota logic (not bypassed)
    monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
    return QuotaEnforcer(offline_mode=False)


class TestDeviceFingerprinting:
    """Tests for device ID generation."""

    def test_device_id_is_stable(self, quota_enforcer):
        """Device ID should be consistent across calls."""
        device_id_1 = quota_enforcer.get_device_id()
        device_id_2 = quota_enforcer.get_device_id()

        assert device_id_1 == device_id_2

    def test_device_id_is_sha256_hash(self, quota_enforcer):
        """Device ID should be SHA256 hash (64 hex chars)."""
        device_id = quota_enforcer.get_device_id()

        assert len(device_id) == 64
        assert all(c in "0123456789abcdef" for c in device_id)

    def test_device_id_uses_platform_info(self, quota_enforcer):
        """Device ID should be based on platform identifiers."""
        with patch("platform.node", return_value="test-machine"):
            device_id_1 = quota_enforcer.get_device_id()

        with patch("platform.node", return_value="different-machine"):
            device_id_2 = quota_enforcer.get_device_id()

        assert device_id_1 != device_id_2


class TestFreeTierQuota:
    """Tests for free tier quota enforcement."""

    def test_free_tier_has_3_validations_per_month(self, quota_enforcer, monkeypatch):
        """Free tier should get 3 validations per month."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        status = quota_enforcer.check_quota()

        assert status.tier == "free"
        assert status.quota_limit == 3
        assert not status.quota_exceeded

    def test_free_tier_blocks_after_quota_exceeded(self, quota_enforcer, monkeypatch):
        """Free tier should block validation after quota exceeded."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Use up quota
        for _ in range(3):
            quota_enforcer.record_validation()

        status = quota_enforcer.check_quota()

        assert status.tier == "free"
        assert status.validations_this_month == 3
        assert status.quota_exceeded

    def test_free_tier_allows_within_quota(self, quota_enforcer, monkeypatch):
        """Free tier should allow validation within quota."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Use 2 validations
        for _ in range(2):
            quota_enforcer.record_validation()

        status = quota_enforcer.check_quota()

        assert status.tier == "free"
        assert status.validations_this_month == 2
        assert not status.quota_exceeded

    def test_quota_counter_increments(self, quota_enforcer, monkeypatch):
        """Validation count should increment correctly."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        initial_status = quota_enforcer.check_quota()
        assert initial_status.validations_this_month == 0

        quota_enforcer.record_validation()
        status_1 = quota_enforcer.check_quota()
        assert status_1.validations_this_month == 1

        quota_enforcer.record_validation()
        status_2 = quota_enforcer.check_quota()
        assert status_2.validations_this_month == 2


class TestPaidTierBypass:
    """Tests for paid tier unlimited validations."""

    def test_paid_tier_bypasses_quota_with_api_key(self, quota_enforcer, monkeypatch):
        """Users with API key should have unlimited quota."""
        monkeypatch.setenv("DEEPSWEEP_API_KEY", "test_api_key_12345")

        status = quota_enforcer.check_quota()

        assert status.tier == "starter"
        assert status.quota_limit == 999999  # Unlimited
        assert not status.quota_exceeded
        assert status.has_api_key

    def test_paid_tier_works_offline(self, quota_enforcer, monkeypatch):
        """Paid users should work offline without quota enforcement."""
        monkeypatch.setenv("DEEPSWEEP_API_KEY", "test_api_key_12345")

        # Even after many validations, quota should not be enforced
        for _ in range(100):
            quota_enforcer.record_validation()

        status = quota_enforcer.check_quota()

        assert status.has_api_key
        assert not status.quota_exceeded

    def test_paid_tier_no_local_tracking(self, quota_enforcer, monkeypatch):
        """Paid tier should not track validations locally."""
        monkeypatch.setenv("DEEPSWEEP_API_KEY", "test_api_key_12345")

        # Record validations
        for _ in range(5):
            quota_enforcer.record_validation()

        # Paid tier always reports 0 validations (unlimited)
        status = quota_enforcer.check_quota()
        assert status.validations_this_month == 0


class TestMonthlyReset:
    """Tests for monthly quota reset."""

    def test_quota_resets_on_new_month(self, quota_enforcer, monkeypatch):
        """Quota should reset at the start of a new month."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Use up quota in "previous month"
        for _ in range(3):
            quota_enforcer.record_validation()

        # Simulate new month
        last_month = "2025-12"
        current_month = "2026-01"

        # Manually set old month in local storage
        local_data = quota_enforcer._load_local_quota()
        if local_data:
            local_data.last_reset_month = last_month
            quota_enforcer._save_local_quota(local_data)

        # Check quota should detect new month and reset
        with patch("deepsweep.quota.date") as mock_date:
            mock_date.today.return_value.strftime.return_value = current_month
            status = quota_enforcer.check_quota()

        assert status.validations_this_month == 0
        assert not status.quota_exceeded
        assert status.last_reset_month == current_month

    def test_quota_persists_within_same_month(self, quota_enforcer, monkeypatch):
        """Quota should persist within the same month."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        current_month = date.today().strftime("%Y-%m")

        # Use 2 validations
        quota_enforcer.record_validation()
        quota_enforcer.record_validation()

        status = quota_enforcer.check_quota()

        assert status.validations_this_month == 2
        assert status.last_reset_month == current_month


class TestOfflineFallback:
    """Tests for offline mode and local fallback."""

    def test_offline_mode_uses_local_storage(self, quota_enforcer, monkeypatch):
        """Offline mode should use local storage only."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Record validation offline
        quota_enforcer.record_validation()

        status = quota_enforcer.check_quota()

        assert status.is_offline
        assert status.validations_this_month == 1

    def test_offline_mode_enforces_free_tier_quota(self, quota_enforcer, monkeypatch):
        """Offline free tier users should still hit quota limits."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Use up quota offline
        for _ in range(3):
            quota_enforcer.record_validation()

        status = quota_enforcer.check_quota()

        assert status.is_offline
        assert status.quota_exceeded

    def test_smart_offline_for_known_paid_users(self, tmp_path, monkeypatch):
        """Users who were paid tier should work offline."""
        monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
        config_dir = tmp_path / "deepsweep"
        config_dir.mkdir(parents=True, exist_ok=True)
        quota_file = config_dir / "quota.json"

        # Simulate user who previously had paid tier
        previous_status = QuotaStatus(
            device_id="test_device",
            validations_this_month=10,
            quota_limit=999999,
            tier="starter",
            last_validation_date="2026-01-15T10:00:00",
            last_reset_month="2026-01",
            quota_exceeded=False,
            has_api_key=False,  # No longer has key
            is_offline=True,
        )

        with quota_file.open("w") as f:
            json.dump(previous_status.to_dict(), f)

        # Create enforcer in offline mode
        monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)
        enforcer = QuotaEnforcer(offline_mode=True)

        status = enforcer.check_quota()

        # Should allow validation because user previously had paid tier
        assert status.tier == "starter"
        assert not status.quota_exceeded
        assert status.is_offline


class TestLocalStorage:
    """Tests for local storage persistence."""

    def test_local_storage_persists_across_instances(self, tmp_path, monkeypatch):
        """Quota data should persist across enforcer instances."""
        monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
        config_dir = tmp_path / "deepsweep"
        monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # First instance
        enforcer_1 = QuotaEnforcer(offline_mode=True)
        enforcer_1.record_validation()
        enforcer_1.record_validation()

        # Second instance should see same data
        enforcer_2 = QuotaEnforcer(offline_mode=True)
        status = enforcer_2.check_quota()

        assert status.validations_this_month == 2

    def test_local_storage_handles_corrupted_file(self, tmp_path, monkeypatch):
        """Should handle corrupted quota file gracefully."""
        config_dir = tmp_path / "deepsweep"
        config_dir.mkdir(parents=True, exist_ok=True)
        quota_file = config_dir / "quota.json"

        # Write corrupted JSON
        with quota_file.open("w") as f:
            f.write("{invalid json!!")

        monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
        enforcer = QuotaEnforcer(offline_mode=True)

        status = enforcer.check_quota()

        # Should create fresh status
        assert status.validations_this_month == 0
        assert not status.quota_exceeded

    def test_clear_removes_quota_data(self, quota_enforcer, monkeypatch):
        """Clear should remove all quota data."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Create some data
        quota_enforcer.record_validation()
        assert quota_enforcer.quota_file.exists()

        # Clear
        quota_enforcer.clear()

        # File should be removed
        assert not quota_enforcer.quota_file.exists()


class TestUpgradeMessage:
    """Tests for upgrade message generation."""

    def test_upgrade_message_includes_usage(self, quota_enforcer):
        """Upgrade message should include usage stats."""
        message = quota_enforcer.get_upgrade_message(
            validations_used=3, quota_limit=3
        )

        assert "3 of 3" in message
        assert "free checks" in message

    def test_upgrade_message_includes_pricing(self, quota_enforcer):
        """Upgrade message should include pricing info."""
        message = quota_enforcer.get_upgrade_message(
            validations_used=3, quota_limit=3
        )

        assert "Starter Plan ($9/month)" in message
        assert "Pro Plan ($29/month)" in message
        assert "https://deepsweep.ai/pricing" in message

    def test_upgrade_message_no_emoji(self, quota_enforcer):
        """Upgrade message should not contain emoji."""
        message = quota_enforcer.get_upgrade_message(
            validations_used=3, quota_limit=3
        )

        # Check for common emoji characters
        emoji_chars = ["🚀", "💎", "✅", "🔥", "⚡", "🎉", "👍"]
        for emoji in emoji_chars:
            assert emoji not in message

    def test_upgrade_message_mentions_reset(self, quota_enforcer):
        """Upgrade message should mention monthly reset."""
        message = quota_enforcer.get_upgrade_message(
            validations_used=3, quota_limit=3
        )

        assert "reset" in message.lower()
        assert "next month" in message.lower()


class TestConvenienceFunctions:
    """Tests for module-level convenience functions."""

    def test_check_quota_returns_tuple(self, monkeypatch, tmp_path):
        """check_quota() should return (bool, str | None)."""
        monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
        monkeypatch.setattr(
            "deepsweep.quota.QuotaEnforcer.CONFIG_DIR", tmp_path / "deepsweep"
        )
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        can_validate, message = check_quota(offline_mode=True)

        assert isinstance(can_validate, bool)
        assert can_validate  # First check should pass
        assert message is None

    def test_check_quota_blocks_after_limit(self, monkeypatch, tmp_path):
        """check_quota() should block after quota exceeded."""
        monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
        config_dir = tmp_path / "deepsweep"
        monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Use up quota
        for _ in range(3):
            record_validation(offline_mode=True)

        can_validate, message = check_quota(offline_mode=True)

        assert not can_validate
        assert message is not None
        assert "3 of 3" in message

    def test_record_validation_increments_count(self, monkeypatch, tmp_path):
        """record_validation() should increment usage count."""
        monkeypatch.delenv("DEEPSWEEP_SKIP_QUOTA", raising=False)
        config_dir = tmp_path / "deepsweep"
        monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        # Record validations
        record_validation(offline_mode=True)
        record_validation(offline_mode=True)

        # Check count
        enforcer = QuotaEnforcer(offline_mode=True)
        status = enforcer.check_quota()

        assert status.validations_this_month == 2


class TestEdgeCases:
    """Tests for edge cases and error handling."""

    def test_handles_missing_config_directory(self, tmp_path, monkeypatch):
        """Should create config directory if it doesn't exist."""
        config_dir = tmp_path / "nonexistent" / "deepsweep"
        monkeypatch.setattr("deepsweep.quota.QuotaEnforcer.CONFIG_DIR", config_dir)

        enforcer = QuotaEnforcer(offline_mode=True)

        assert enforcer.config_dir.exists()

    def test_offline_mode_from_environment(self, monkeypatch):
        """Should detect offline mode from DEEPSWEEP_OFFLINE env var."""
        monkeypatch.setenv("DEEPSWEEP_OFFLINE", "1")

        enforcer = QuotaEnforcer()

        assert enforcer.offline_mode

    def test_api_key_from_environment(self, quota_enforcer, monkeypatch):
        """Should detect API key from DEEPSWEEP_API_KEY env var."""
        monkeypatch.setenv("DEEPSWEEP_API_KEY", "sk_test_12345")

        api_key = quota_enforcer._get_api_key()

        assert api_key == "sk_test_12345"

    def test_initial_quota_status_for_new_device(self, quota_enforcer, monkeypatch):
        """New device should get fresh quota status."""
        monkeypatch.delenv("DEEPSWEEP_API_KEY", raising=False)

        status = quota_enforcer.check_quota()

        assert status.validations_this_month == 0
        assert status.tier == "free"
        assert status.quota_limit == 3
        assert not status.quota_exceeded
        assert status.last_reset_month == date.today().strftime("%Y-%m")
