"""Tests for ROI tracking in investments."""

from decimal import Decimal
from datetime import date, timedelta
from django.test import TestCase
from django.utils import timezone

from apps.users.models import User
from apps.investments.models import Investment, Reward
from apps.investments.cron_tasks import DistributeRewardsTask


def get_task():
    """Helper to create task instance with required execution_date."""
    return DistributeRewardsTask(execution_date=date.today())


class ROITrackingTestCase(TestCase):
    """Test ROI accumulation and percentage calculations."""

    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            telegram_user_id=111111,
            first_name="Test User",
        )
        self.user.credit_balance = Decimal('10000.00000000')
        self.user.save()

    def test_new_investment_has_zero_roi(self):
        """Test newly created investment starts with zero ROI totals."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('1000.00000000'),
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_PENDING,
            start_date=timezone.now(),
            end_date=timezone.now() + timedelta(days=120),
        )
        
        self.assertEqual(investment.roi_total_amount, Decimal('0.00000000'))
        self.assertEqual(investment.roi_total_percent, Decimal('0.0000'))

    def test_roi_accumulation_single_reward(self):
        """Test ROI totals update correctly after single reward distribution."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('1000.00000000'),
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=2),
            end_date=timezone.now() + timedelta(days=118),
        )
        
        # Create a pending reward
        reward_amount = Decimal('10.00000000')  # 1% of 1000
        reward = Reward.objects.create(
            investment=investment,
            amount=reward_amount,
            reward_date=date.today() - timedelta(days=1),
        )
        
        # Distribute the reward
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        # Refresh investment
        investment.refresh_from_db()
        
        self.assertEqual(investment.roi_total_amount, reward_amount)
        self.assertEqual(investment.roi_total_percent, Decimal('1.0000'))  # 10/1000 * 100 = 1%

    def test_roi_accumulation_multiple_rewards(self):
        """Test ROI totals accumulate correctly across multiple rewards."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('1000.00000000'),
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=5),
            end_date=timezone.now() + timedelta(days=115),
        )
        
        # Create and distribute 3 rewards
        total_expected = Decimal('0.00000000')
        for i in range(3):
            reward_amount = Decimal('10.00000000')
            reward = Reward.objects.create(
                investment=investment,
                amount=reward_amount,
                reward_date=date.today() - timedelta(days=3-i),
            )
            
            task = get_task()
            task._distribute_rewards(reward.reward_date, timezone.now())
            
            total_expected += reward_amount
        
        investment.refresh_from_db()
        
        self.assertEqual(investment.roi_total_amount, total_expected)
        self.assertEqual(investment.roi_total_percent, Decimal('3.0000'))  # 30/1000 * 100 = 3%

    def test_roi_percentage_calculation_precision(self):
        """Test ROI percentage maintains correct precision."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('333.33333333'),
            tier=1,
            daily_reward_rate=Decimal('1.50'),
            duration_days=120,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=2),
            end_date=timezone.now() + timedelta(days=118),
        )
        
        # Reward: 1.5% of 333.33333333 = 4.99999999995
        reward_amount = Decimal('4.99999999')  # Quantized to 8 decimals
        reward = Reward.objects.create(
            investment=investment,
            amount=reward_amount,
            reward_date=date.today() - timedelta(days=1),
        )
        
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        
        # Expected: (4.99999999 / 333.33333333) * 100 = 1.5000000...
        self.assertAlmostEqual(
            float(investment.roi_total_percent),
            1.50,
            places=2
        )

    def test_roi_zero_division_protection(self):
        """Test ROI percentage calculation handles edge case of zero investment amount."""
        # This shouldn't happen due to validation, but test data integrity
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('0.00000001'),  # Minimal amount
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=2),
            end_date=timezone.now() + timedelta(days=118),
        )
        
        reward_amount = Decimal('0.00000001')
        reward = Reward.objects.create(
            investment=investment,
            amount=reward_amount,
            reward_date=date.today() - timedelta(days=1),
        )
        
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        
        # Should handle calculation without error
        self.assertIsNotNone(investment.roi_total_percent)

    def test_roi_large_amounts(self):
        """Test ROI tracking with very large investment amounts."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('999999.99999999'),
            tier=6,
            daily_reward_rate=Decimal('3.50'),
            duration_days=180,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=2),
            end_date=timezone.now() + timedelta(days=178),
        )
        
        # 3.5% of 999999.99999999 = 34999.99999999965
        reward_amount = Decimal('34999.99999999')
        reward = Reward.objects.create(
            investment=investment,
            amount=reward_amount,
            reward_date=date.today() - timedelta(days=1),
        )
        
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        
        self.assertEqual(investment.roi_total_amount, reward_amount)
        self.assertAlmostEqual(
            float(investment.roi_total_percent),
            3.50,
            places=2
        )

    def test_roi_tracking_across_investment_lifecycle(self):
        """Test ROI tracking from pending -> active -> completed."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('1000.00000000'),
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_PENDING,
            start_date=timezone.now(),
            end_date=timezone.now() + timedelta(days=120),
        )
        
        # Initially zero
        self.assertEqual(investment.roi_total_amount, Decimal('0.00000000'))
        
        # Move to active and add reward
        investment.status = Investment.STATUS_ACTIVE
        investment.save()
        
        reward = Reward.objects.create(
            investment=investment,
            amount=Decimal('10.00000000'),
            reward_date=date.today(),
        )
        
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        self.assertEqual(investment.roi_total_amount, Decimal('10.00000000'))
        
        # Move to completed - ROI should persist
        investment.status = Investment.STATUS_COMPLETED
        investment.save()
        investment.refresh_from_db()
        
        self.assertEqual(investment.roi_total_amount, Decimal('10.00000000'))
        self.assertEqual(investment.roi_total_percent, Decimal('1.0000'))

    def test_roi_idempotency(self):
        """Test distributing the same reward twice doesn't double-count."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('1000.00000000'),
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=2),
            end_date=timezone.now() + timedelta(days=118),
        )
        
        reward_amount = Decimal('10.00000000')
        reward = Reward.objects.create(
            investment=investment,
            amount=reward_amount,
            reward_date=date.today() - timedelta(days=1),
        )
        
        # Distribute once
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        first_total = investment.roi_total_amount
        
        # Try to distribute again (should be skipped as already distributed)
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        
        # Should not change
        self.assertEqual(investment.roi_total_amount, first_total)

    def test_roi_fractional_percentages(self):
        """Test ROI percentage calculation with fractional results."""
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('777.77777777'),
            tier=2,
            daily_reward_rate=Decimal('1.23'),
            duration_days=130,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now() - timedelta(days=2),
            end_date=timezone.now() + timedelta(days=128),
        )
        
        # 1.23% of 777.77777777 = 9.56666666...
        reward_amount = Decimal('9.56666666')
        reward = Reward.objects.create(
            investment=investment,
            amount=reward_amount,
            reward_date=date.today() - timedelta(days=1),
        )
        
        task = get_task()
        task._distribute_rewards(reward.reward_date, timezone.now())
        
        investment.refresh_from_db()
        
        # Expected: (9.56666666 / 777.77777777) * 100 ≈ 1.23%
        self.assertAlmostEqual(
            float(investment.roi_total_percent),
            1.23,
            places=2
        )

    def test_roi_serializer_output(self):
        """Test ROI fields are included in API serialization."""
        from rest_framework.test import APIClient
        from apps.investments.serializers import InvestmentSerializer
        
        investment = Investment.objects.create(
            user=self.user,
            amount=Decimal('1000.00000000'),
            tier=1,
            daily_reward_rate=Decimal('1.00'),
            duration_days=120,
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now(),
            end_date=timezone.now() + timedelta(days=120),
            roi_total_amount=Decimal('50.00000000'),
            roi_total_percent=Decimal('5.0000'),
        )
        
        serializer = InvestmentSerializer(investment)
        data = serializer.data
        
        self.assertIn('roi_total_amount', data)
        self.assertIn('roi_total_percent', data)
        self.assertEqual(Decimal(str(data['roi_total_amount'])), Decimal('50.00000000'))
        self.assertEqual(Decimal(str(data['roi_total_percent'])), Decimal('5.0000'))
