"""API tests for investments and rewards."""

from __future__ import annotations

from datetime import timedelta
from decimal import Decimal

from django.urls import reverse
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APIClient, APITestCase

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


def get_results(payload):
    """Handle paginated and non-paginated DRF responses."""
    if isinstance(payload, dict) and "results" in payload:
        return payload["results"]
    return payload


class InvestmentApiTests(APITestCase):
    def setUp(self) -> None:
        self.user = User.objects.create_user(
            telegram_user_id=111,
            first_name="Investor",
            username="investor",
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_create_investment_derives_tier_and_schedule(self):
        """POST /investments sets tier, rate, duration, start/end dates."""
        # Set up sufficient balance for investment
        self.user.credit_balance = Decimal("1500.00000000")
        self.user.save()
        
        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "1500"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        investment = Investment.objects.get(id=response.data["id"])
        self.assertEqual(investment.tier, 4)
        self.assertEqual(investment.daily_reward_rate, Decimal("2.50"))
        self.assertEqual(investment.duration_days, 150)
        self.assertEqual(investment.status, Investment.STATUS_PENDING)

        # Start date is now-ish; end date matches duration.
        self.assertLess(timezone.now() - investment.start_date, timedelta(seconds=5))
        self.assertEqual(
            (investment.end_date - investment.start_date).days,
            investment.duration_days,
        )

    def test_create_rejects_amount_below_minimum(self):
        """Amount under $10 is rejected with validation error."""
        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "5"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)

    def test_create_rejects_non_numeric_amount(self):
        """Non-numeric amount payload returns a 400."""
        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "abc"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)

    def test_create_assigns_highest_tier_for_large_amount(self):
        """Amounts > $5,000 receive tier 6 metadata."""
        # Set up sufficient balance for investment
        self.user.credit_balance = Decimal("6000.00000000")
        self.user.save()
        
        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "6000"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        investment = Investment.objects.get(id=response.data["id"])
        self.assertEqual(investment.tier, 6)
        self.assertEqual(investment.daily_reward_rate, Decimal("3.50"))
        self.assertEqual(investment.duration_days, 180)

    def test_list_orders_investments_descending_by_created(self):
        """Ordering backend returns newest investment first."""
        tier_info = Investment.get_tier_info(Decimal("100"))
        now = timezone.now()
        first = Investment.objects.create(
            user=self.user,
            amount=Decimal("100"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_PENDING,
            start_date=now,
            end_date=now + timedelta(days=tier_info["duration"]),
        )
        later = Investment.objects.create(
            user=self.user,
            amount=Decimal("150"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_PENDING,
            start_date=now,
            end_date=now + timedelta(days=tier_info["duration"]),
        )

        url = reverse("investment-list")
        response = self.client.get(url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = get_results(response.data)
        self.assertEqual([item["id"] for item in results], [later.id, first.id])

    def test_retrieve_other_users_investment_returns_404(self):
        """Users cannot fetch another user's investment detail."""
        other_user = User.objects.create_user(
            telegram_user_id=999, first_name="Other", username="other-detail"
        )
        tier_info = Investment.get_tier_info(Decimal("120"))
        now = timezone.now()
        other_inv = Investment.objects.create(
            user=other_user,
            amount=Decimal("120"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_PENDING,
            start_date=now,
            end_date=now + timedelta(days=tier_info["duration"]),
        )

        url = reverse("investment-detail", args=[other_inv.id])
        response = self.client.get(url)

        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_list_is_scoped_to_authenticated_user(self):
        """Authenticated user only sees their own investments."""
        other_user = User.objects.create_user(
            telegram_user_id=222, first_name="Other", username="other"
        )
        tier_info = Investment.get_tier_info(Decimal("100"))
        now = timezone.now()
        user_inv = Investment.objects.create(
            user=self.user,
            amount=Decimal("100"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_PENDING,
            start_date=now,
            end_date=now + timedelta(days=tier_info["duration"]),
        )
        Investment.objects.create(
            user=other_user,
            amount=Decimal("200"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_PENDING,
            start_date=now,
            end_date=now + timedelta(days=tier_info["duration"]),
        )

        url = reverse("investment-list")
        response = self.client.get(url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = get_results(response.data)
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]["id"], user_inv.id)

    def test_requires_authentication(self):
        """Unauthenticated requests are rejected."""
        client = APIClient()
        response = client.get(reverse("investment-list"))
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)


class RewardApiTests(APITestCase):
    def setUp(self) -> None:
        self.user = User.objects.create_user(
            telegram_user_id=333,
            first_name="RewardUser",
            username="rewarduser",
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

        tier_info = Investment.get_tier_info(Decimal("500"))
        now = timezone.now()
        self.investment = Investment.objects.create(
            user=self.user,
            amount=Decimal("500"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_ACTIVE,
            start_date=now,
            end_date=now + timedelta(days=tier_info["duration"]),
        )

    def test_list_filters_by_investment(self):
        """GET /rewards supports filtering by investment_id."""
        Reward.objects.create(
            investment=self.investment,
            amount=Decimal("5.00000000"),
            reward_date=timezone.now().date(),
        )
        other_user = User.objects.create_user(
            telegram_user_id=444, first_name="Other", username="other2"
        )
        tier_info = Investment.get_tier_info(Decimal("50"))
        other_inv = Investment.objects.create(
            user=other_user,
            amount=Decimal("50"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now(),
            end_date=timezone.now() + timedelta(days=tier_info["duration"]),
        )
        Reward.objects.create(
            investment=other_inv,
            amount=Decimal("1.00000000"),
            reward_date=timezone.now().date(),
        )

        url = reverse("reward-list")
        response = self.client.get(url, {"investment_id": self.investment.id})

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = get_results(response.data)
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]["investment"], self.investment.id)

    def test_list_orders_rewards_descending(self):
        """Ordering returns newest reward date first."""
        today = timezone.now().date()
        yesterday = today - timedelta(days=1)
        Reward.objects.create(
            investment=self.investment,
            amount=Decimal("5.00000000"),
            reward_date=yesterday,
        )
        Reward.objects.create(
            investment=self.investment,
            amount=Decimal("6.00000000"),
            reward_date=today,
        )

        response = self.client.get(reverse("reward-list"))

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = get_results(response.data)
        dates = [item["reward_date"] for item in results]
        self.assertEqual(dates[0], today.isoformat())
        self.assertEqual(dates[1], yesterday.isoformat())

    def test_rewards_are_scoped_to_user(self):
        """Authenticated user only sees rewards for their investments."""
        Reward.objects.create(
            investment=self.investment,
            amount=Decimal("5.00000000"),
            reward_date=timezone.now().date(),
        )
        other_user = User.objects.create_user(
            telegram_user_id=555, first_name="Other", username="other3"
        )
        tier_info = Investment.get_tier_info(Decimal("60"))
        other_inv = Investment.objects.create(
            user=other_user,
            amount=Decimal("60"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now(),
            end_date=timezone.now() + timedelta(days=tier_info["duration"]),
        )
        Reward.objects.create(
            investment=other_inv,
            amount=Decimal("1.00000000"),
            reward_date=timezone.now().date(),
        )

        response = self.client.get(reverse("reward-list"))

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = get_results(response.data)
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]["investment"], self.investment.id)

    def test_rewards_filter_other_users_investment_returns_empty(self):
        """Filtering by another user's investment returns no results."""
        other_user = User.objects.create_user(
            telegram_user_id=556, first_name="Other", username="other4"
        )
        tier_info = Investment.get_tier_info(Decimal("70"))
        other_inv = Investment.objects.create(
            user=other_user,
            amount=Decimal("70"),
            tier=tier_info["tier"],
            daily_reward_rate=tier_info["rate"],
            duration_days=tier_info["duration"],
            status=Investment.STATUS_ACTIVE,
            start_date=timezone.now(),
            end_date=timezone.now() + timedelta(days=tier_info["duration"]),
        )
        Reward.objects.create(
            investment=other_inv,
            amount=Decimal("1.00000000"),
            reward_date=timezone.now().date(),
        )

        url = reverse("reward-list")
        response = self.client.get(url, {"investment_id": other_inv.id})

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = get_results(response.data)
        self.assertEqual(results, [])

    def test_rewards_require_authentication(self):
        """Unauthenticated reward access returns 401."""
        client = APIClient()
        response = client.get(reverse("reward-list"))

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
