"""Bid negotiation: single active bid, trip.agreed_price, auto-assign on transporter accept."""
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, Shipment, Trip

User = get_user_model()


class BidNegotiationAgreedPriceTests(TestCase):
    """Transporter accept assigns trip and notifies shipper."""

    def setUp(self):
        self.shipper = User.objects.create_user(
            'nego-shipper@test.com', email='nego-shipper@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)

        self.transporter = User.objects.create_user(
            'nego-trans@test.com', email='nego-trans@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='NEGO-1',
            load_capacity=Decimal('3000'),
            is_verified=True,
            is_active=True,
        )

        self.shipment = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='A',
            delivery_address='B',
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=True,
            country_to_country=False,
            status=Shipment.Status.PUBLISHED,
            suggested_price=Decimal('404.43'),
            currency='PKR',
        )

        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}')

        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}')

    def test_transporter_accept_counter_assigns_trip_to_driver(self):
        r1 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '410.00'},
            format='json',
        )
        self.assertEqual(r1.status_code, 201)
        bid_id = r1.json()['data']['id']

        self.shipper_client.post(
            f'/api/shipper/bids/{bid_id}/counter/',
            {'counter_amount': '450.00'},
            format='json',
        )

        r3 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'ACCEPT'},
            format='json',
        )
        self.assertEqual(r3.status_code, 200, r3.content)
        data = r3.json()['data']
        self.assertEqual(data['status'], 'ACCEPTED')
        self.assertEqual(str(data['amount']), '450.00')
        self.assertEqual(str(data['agreed_price']), '450.00')
        self.assertIn('trip_id', data)

        self.assertEqual(Bid.objects.filter(shipment=self.shipment, transporter=self.transporter).count(), 1)

        trip = Trip.objects.get(pk=data['trip_id'])
        self.assertEqual(trip.agreed_price, Decimal('450.00'))
        self.assertEqual(trip.transporter_id, self.transporter.id)
        self.assertEqual(trip.assigned_driver_id, self.transporter.id)
        self.assertEqual(trip.accepted_bid.amount, Decimal('450.00'))

        detail = self.shipper_client.get(f'/api/shipper/trips/{trip.id}/')
        self.assertEqual(detail.status_code, 200)
        self.assertEqual(str(detail.json()['data']['agreed_price']), '450.00')

    def test_transporter_accept_proposed_rate_assigns_trip(self):
        r = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'ACCEPT'},
            format='json',
        )
        self.assertEqual(r.status_code, 200, r.content)
        data = r.json()['data']
        self.assertEqual(data['status'], 'ACCEPTED')
        self.assertEqual(str(data['amount']), '404.43')
        self.assertEqual(str(data['agreed_price']), '404.43')
        trip = Trip.objects.get(pk=data['trip_id'])
        self.assertEqual(trip.assigned_driver_id, self.transporter.id)
        self.shipment.refresh_from_db()
        self.assertEqual(self.shipment.status, Shipment.Status.ASSIGNED)

    def test_shipper_cannot_accept_countered_bid_before_transporter_agrees(self):
        r1 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '410.00'},
            format='json',
        )
        bid_id = r1.json()['data']['id']
        self.shipper_client.post(
            f'/api/shipper/bids/{bid_id}/counter/',
            {'counter_amount': '450.00'},
            format='json',
        )
        r_accept = self.shipper_client.post(f'/api/shipper/bids/{bid_id}/accept/', {}, format='json')
        self.assertEqual(r_accept.status_code, 400)
        self.assertIn('counter-offer', r_accept.json()['message'].lower())

    def test_shipper_can_accept_transporter_pending_offer(self):
        r1 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '500.00'},
            format='json',
        )
        bid_id = r1.json()['data']['id']
        r_accept = self.shipper_client.post(f'/api/shipper/bids/{bid_id}/accept/', {}, format='json')
        self.assertEqual(r_accept.status_code, 200, r_accept.content)
        self.assertEqual(str(r_accept.json()['data']['agreed_price']), '500.00')

    def test_transporter_counter_updates_single_bid(self):
        r1 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '100.00'},
            format='json',
        )
        self.assertEqual(r1.status_code, 201)
        bid_id = r1.json()['data']['id']

        r2 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '120.00'},
            format='json',
        )
        self.assertEqual(r2.status_code, 200)
        self.assertEqual(r2.json()['data']['id'], bid_id)
        self.assertEqual(str(r2.json()['data']['amount']), '120.00')
        self.assertEqual(Bid.objects.filter(shipment=self.shipment, transporter=self.transporter).count(), 1)

    @patch('api.views.notify_users')
    def test_transporter_accept_counter_notifies_shipper(self, mock_notify):
        r1 = self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '410.00'},
            format='json',
        )
        bid_id = r1.json()['data']['id']
        self.shipper_client.post(
            f'/api/shipper/bids/{bid_id}/counter/',
            {'counter_amount': '450.00'},
            format='json',
        )
        mock_notify.reset_mock()
        self.transporter_client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'ACCEPT'},
            format='json',
        )
        mock_notify.assert_called_once()
        self.assertEqual(mock_notify.call_args[0][0], [self.shipper.id])
        self.assertEqual(mock_notify.call_args[0][1], 'bid_accepted')
