from decimal import Decimal
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.test import TestCase
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

from accounts.models import ShipperProfile, TransporterProfile, UserRole, Vehicle
from core.models import Bid, Conversation, Shipment, Trip

User = get_user_model()


class ShipperBidRejectApiTests(TestCase):
    def setUp(self):
        # 1. Shipper
        self.shipper = User.objects.create_user('shipper-rej@test.com', email='shipper-rej@test.com', password='secret')
        UserRole.objects.create(user=self.shipper, role=UserRole.Role.SHIPPER, phone='', language='en')
        ShipperProfile.objects.create(user=self.shipper, account_type=ShipperProfile.AccountType.INDIVIDUAL)

        # 2. Other Shipper
        self.other_shipper = User.objects.create_user('other-shipper@test.com', email='other-shipper@test.com', password='secret')
        UserRole.objects.create(user=self.other_shipper, role=UserRole.Role.SHIPPER, phone='', language='en')
        ShipperProfile.objects.create(user=self.other_shipper, account_type=ShipperProfile.AccountType.INDIVIDUAL)

        # 3. Transporter
        self.transporter = User.objects.create_user('transporter-rej@test.com', email='transporter-rej@test.com', password='secret')
        UserRole.objects.create(user=self.transporter, role=UserRole.Role.TRANSPORTER, phone='', language='en')
        TransporterProfile.objects.create(
            user=self.transporter,
            account_type=TransporterProfile.AccountType.DRIVER,
            local=True,
            country_to_country=False,
            documents_verified=True,
        )
        Vehicle.objects.create(
            owner=self.transporter,
            vehicle_type='Flatbed',
            registration_number='REJ-999',
            load_capacity=Decimal('3000'),
            is_verified=True,
            is_active=True,
        )

        # 4. Shipment
        self.shipment = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Pickup Loc',
            delivery_address='Delivery Loc',
            cargo_type='Steel',
            weight='2 tons',
            vehicle_type_required='Flatbed',
            local=True,
            country_to_country=False,
            status=Shipment.Status.PUBLISHED,
        )

        # 5. Active Bid
        self.bid = Bid.objects.create(
            shipment=self.shipment,
            transporter=self.transporter,
            amount=Decimal('1500.00'),
            status=Bid.Status.PENDING,
            message='Initial bid offer',
        )

        shipper_token, _ = Token.objects.get_or_create(user=self.shipper)
        self.shipper_client = APIClient()
        self.shipper_client.credentials(HTTP_AUTHORIZATION=f'Bearer {shipper_token.key}')

        other_token, _ = Token.objects.get_or_create(user=self.other_shipper)
        self.other_client = APIClient()
        self.other_client.credentials(HTTP_AUTHORIZATION=f'Bearer {other_token.key}')

        transporter_token, _ = Token.objects.get_or_create(user=self.transporter)
        self.transporter_client = APIClient()
        self.transporter_client.credentials(HTTP_AUTHORIZATION=f'Bearer {transporter_token.key}')

    @patch('api.views.notify_users')
    def test_shipper_reject_pending_bid_success(self, mock_notify):
        response = self.shipper_client.post(
            f'/api/shipper/bids/{self.bid.id}/reject/',
            {'reason': 'Price is too high for this load.'},
            format='json',
        )
        self.assertEqual(response.status_code, 200)
        data = response.json()['data']
        self.assertEqual(data['status'], 'REJECTED')
        self.assertEqual(data['message'], 'Price is too high for this load.')

        self.bid.refresh_from_db()
        self.assertEqual(self.bid.status, Bid.Status.REJECTED)
        self.assertEqual(self.bid.message, 'Price is too high for this load.')

        # Verify notification sent to transporter
        mock_notify.assert_called_once()
        call_args = mock_notify.call_args
        self.assertEqual(call_args[0][0], [self.transporter.id])
        self.assertEqual(call_args[0][1], 'bid_rejected')

    @patch('api.views.notify_users')
    def test_shipper_reject_countered_bid_success(self, mock_notify):
        self.bid.status = Bid.Status.COUNTERED
        self.bid.counter_amount = Decimal('1400.00')
        self.bid.save()

        response = self.shipper_client.post(
            f'/api/shipper/bids/{self.bid.id}/reject/',
            format='json',
        )
        self.assertEqual(response.status_code, 200)
        self.bid.refresh_from_db()
        self.assertEqual(self.bid.status, Bid.Status.REJECTED)

    def test_reject_bid_unauthorized_shipper_rejected(self):
        # Other shipper cannot reject someone else's bid
        response = self.other_client.post(
            f'/api/shipper/bids/{self.bid.id}/reject/',
            format='json',
        )
        self.assertEqual(response.status_code, 404)

        # Transporter cannot call shipper reject endpoint
        tr_res = self.transporter_client.post(
            f'/api/shipper/bids/{self.bid.id}/reject/',
            format='json',
        )
        self.assertEqual(tr_res.status_code, 403)

    def test_reject_already_accepted_bid_fails(self):
        self.bid.status = Bid.Status.ACCEPTED
        self.bid.save()

        response = self.shipper_client.post(
            f'/api/shipper/bids/{self.bid.id}/reject/',
            format='json',
        )
        self.assertEqual(response.status_code, 400)
        self.assertIn('Cannot reject', response.json()['message'])

    def test_reject_already_rejected_bid_fails(self):
        self.bid.status = Bid.Status.REJECTED
        self.bid.save()

        response = self.shipper_client.post(
            f'/api/shipper/bids/{self.bid.id}/reject/',
            format='json',
        )
        self.assertEqual(response.status_code, 400)
        self.assertIn('already rejected', response.json()['message'])

    @patch('api.views.notify_users')
    def test_chat_reject_bid_success(self, mock_notify):
        conv = Conversation.objects.create(
            shipment=self.shipment,
            shipper=self.shipper,
            transporter=self.transporter,
        )

        response = self.shipper_client.post(
            f'/api/chats/{conv.id}/reject-bid/',
            {'message': 'Declined offer via chat negotiation.'},
            format='json',
        )
        self.assertEqual(response.status_code, 200)
        data = response.json()['data']
        self.assertEqual(data['status'], 'REJECTED')
        self.assertEqual(data['message'], 'Declined offer via chat negotiation.')

        self.bid.refresh_from_db()
        self.assertEqual(self.bid.status, Bid.Status.REJECTED)
        mock_notify.assert_called_once()

    @patch('api.views.notify_users')
    def test_shipper_counter_bid_notifies_transporter(self, mock_notify):
        response = self.shipper_client.post(
            f'/api/shipper/bids/{self.bid.id}/counter/',
            {'counter_amount': '1400.00', 'message': 'Can you do this rate?'},
            format='json',
        )
        self.assertEqual(response.status_code, 200)
        data = response.json()['data']
        self.assertEqual(data['status'], 'COUNTERED')
        self.assertEqual(str(data['counter_amount']), '1400.00')

        self.bid.refresh_from_db()
        self.assertEqual(self.bid.status, Bid.Status.COUNTERED)

        mock_notify.assert_called_once()
        call_args = mock_notify.call_args
        self.assertEqual(call_args[0][0], [self.transporter.id])
        self.assertEqual(call_args[0][1], 'bid_countered')
        payload = call_args[0][2]
        self.assertEqual(payload['bid_id'], str(self.bid.id))
        self.assertEqual(payload['shipment_id'], str(self.shipment.id))
        self.assertEqual(payload['counter_amount'], '1400.00')

    @patch('api.views.notify_users')
    def test_shipper_accept_bid_notifies_transporter(self, mock_notify):
        response = self.shipper_client.post(
            f'/api/shipper/bids/{self.bid.id}/accept/',
            {},
            format='json',
        )
        self.assertEqual(response.status_code, 200, response.content)
        mock_notify.assert_called_once()
        call_args = mock_notify.call_args
        self.assertEqual(call_args[0][0], [self.transporter.id])
        self.assertEqual(call_args[0][1], 'bid_accepted')
        payload = call_args[0][2]
        self.assertEqual(payload['agreed_price'], '1500.00')
        self.assertIn('trip_id', payload)
        self.assertEqual(payload['bid_id'], str(self.bid.id))
