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 Shipment, Zone

User = get_user_model()


class TransporterLoadDiscoveryApiTests(TestCase):
    def setUp(self):
        self.shipper = User.objects.create_user('shipdisc@test.com', email='shipdisc@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('trdisc@test.com', email='trdisc@test.com', password='secret')
        UserRole.objects.create(user=self.transporter, role=UserRole.Role.TRANSPORTER, phone='123', language='en')
        TransporterProfile.objects.create(
            user=self.transporter,
            account_type=TransporterProfile.AccountType.DRIVER,
            local=True,
            country_to_country=False,
            documents_verified=True,
            tc_id='111',
        )
        Vehicle.objects.create(
            owner=self.transporter,
            vehicle_type='Flatbed',
            registration_number='TR-DISC-111',
            load_capacity=Decimal('5000'),
            is_verified=True,
            is_active=True,
        )

        self.shipment = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='A',
            pickup_lat=Decimal('24.8607000'),
            pickup_lon=Decimal('67.0011000'),
            pickup_country_code='AE',
            delivery_address='B',
            delivery_lat=Decimal('24.9000000'),
            delivery_lon=Decimal('67.0500000'),
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            suggested_price=Decimal('4200.00'),
            local=True,
            country_to_country=False,
            status=Shipment.Status.PUBLISHED,
        )

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

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_includes_suggested_price(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }

        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        loads = r.json()['data']['loads']
        self.assertGreaterEqual(len(loads), 1)
        self.assertEqual(loads[0]['id'], self.shipment.id)
        self.assertEqual(loads[0]['suggested_price'], '4200.00')
        self.assertIn('shipper', loads[0])
        self.assertEqual(loads[0]['shipper']['id'], self.shipper.id)
        self.assertEqual(loads[0]['shipper']['email'], self.shipper.email)

    def test_requires_country_code(self):
        r = self.client.get('/api/transporter/load-discovery/')
        self.assertEqual(r.status_code, 400)
        body = r.json()
        self.assertEqual(body['message'], 'Validation failed.')
        self.assertIn('country_code', body['error'])
        self.assertIsNone(body['data'])

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_returns_loads_within_zone_radius(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'))
        distant = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Far',
            pickup_lat=Decimal('51.5074000'),
            pickup_lon=Decimal('-0.1278000'),
            pickup_country_code='AE',
            delivery_address='B',
            delivery_lat=Decimal('51.5100000'),
            delivery_lon=Decimal('-0.1300000'),
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=True,
            country_to_country=False,
            status=Shipment.Status.PUBLISHED,
        )

        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        data = r.json()['data']
        load_ids = {item['id'] for item in data['loads']}
        self.assertIn(self.shipment.id, load_ids)
        self.assertNotIn(distant.id, load_ids)
        self.assertEqual(data['radius_source'], 'zone')
        self.assertEqual(float(data['radius_km']), 50.0)

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_excludes_wrong_pickup_country(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }
        r = self.client.get('/api/transporter/load-discovery/?country_code=SA')
        self.assertEqual(r.status_code, 200)
        load_ids = {item['id'] for item in r.json()['data']['loads']}
        self.assertNotIn(self.shipment.id, load_ids)

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_excludes_non_matching_vehicle_type(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }
        self.shipment.vehicle_type_required = 'Flat Bed 12m'
        self.shipment.save(update_fields=['vehicle_type_required'])

        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.json()['data']['count'], 0)

        Vehicle.objects.filter(owner=self.transporter).update(vehicle_type='Flat Bed 12m')
        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        self.assertGreaterEqual(r.json()['data']['count'], 1)

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_without_verified_vehicle_still_returns_loads(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }
        Vehicle.objects.filter(owner=self.transporter).update(is_verified=False)
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'))

        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        data = r.json()['data']
        loads = data['loads']
        self.assertGreaterEqual(len(loads), 1)
        self.assertEqual(loads[0]['id'], self.shipment.id)
        self.assertIsNone(loads[0]['matched_vehicle_type'])
        self.assertIsNone(loads[0]['max_capacity_for_type'])
        self.assertEqual(data['radius_source'], 'zone')
        self.assertEqual(float(data['radius_km']), 50.0)

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_without_verified_vehicle_respects_zone_radius(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }
        Vehicle.objects.filter(owner=self.transporter).update(is_verified=False)
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'))
        distant = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Far',
            pickup_lat=Decimal('51.5074000'),
            pickup_lon=Decimal('-0.1278000'),
            pickup_country_code='AE',
            delivery_address='B',
            delivery_lat=Decimal('51.5100000'),
            delivery_lon=Decimal('-0.1300000'),
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=True,
            country_to_country=False,
            status=Shipment.Status.PUBLISHED,
        )

        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        load_ids = {item['id'] for item in r.json()['data']['loads']}
        self.assertIn(self.shipment.id, load_ids)
        self.assertNotIn(distant.id, load_ids)

    def test_bid_requires_verified_vehicle(self):
        Vehicle.objects.filter(owner=self.transporter).update(is_verified=False)
        r = self.client.post(
            f'/api/transporter/shipments/{self.shipment.id}/bid/',
            {'action': 'COUNTER', 'amount': '4100.00'},
            format='json',
        )
        self.assertEqual(r.status_code, 400)
        self.assertEqual(r.json()['error'], 'Missing verified active vehicle.')

    def test_individual_driver_requires_tc_id(self):
        TransporterProfile.objects.filter(user=self.transporter).update(tc_id='')
        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 400)
        self.assertEqual(r.json()['error'], 'Missing tc_id on transporter profile.')

    @patch('api.views.get_latest_device_position')
    def test_local_driver_excludes_c2c_even_when_nearby(self, mock_position):
        mock_position.return_value = {'latitude': 24.8610, 'longitude': 67.0020, 'raw': {}}
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'))
        c2c = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='C2C nearby',
            pickup_lat=Decimal('24.8620000'),
            pickup_lon=Decimal('67.0030000'),
            pickup_country_code='AE',
            delivery_address='Riyadh',
            delivery_country_code='SA',
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=False,
            country_to_country=True,
            status=Shipment.Status.PUBLISHED,
        )
        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        load_ids = {item['id'] for item in r.json()['data']['loads']}
        self.assertIn(self.shipment.id, load_ids)
        self.assertNotIn(c2c.id, load_ids)

    @patch('api.views.get_latest_device_position')
    def test_c2c_driver_sees_nearby_local_and_in_country_c2c(self, mock_position):
        mock_position.return_value = {'latitude': 24.8610, 'longitude': 67.0020, 'raw': {}}
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'))
        TransporterProfile.objects.filter(user=self.transporter).update(
            local=False, country_to_country=True,
        )
        distant_local = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Far local',
            pickup_lat=Decimal('51.5074000'),
            pickup_lon=Decimal('-0.1278000'),
            pickup_country_code='AE',
            delivery_country_code='AE',
            delivery_address='B',
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=True,
            country_to_country=False,
            status=Shipment.Status.PUBLISHED,
        )
        distant_c2c = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Far C2C pickup',
            pickup_lat=Decimal('51.5074000'),
            pickup_lon=Decimal('-0.1278000'),
            pickup_country_code='AE',
            delivery_address='Riyadh',
            delivery_country_code='SA',
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=False,
            country_to_country=True,
            status=Shipment.Status.PUBLISHED,
        )
        same_country_c2c = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Same-country C2C',
            pickup_lat=Decimal('24.8620000'),
            pickup_lon=Decimal('67.0030000'),
            pickup_country_code='AE',
            delivery_address='Abu Dhabi',
            delivery_country_code='AE',
            cargo_type='General',
            weight='1 ton',
            vehicle_type_required='Flatbed',
            local=False,
            country_to_country=True,
            status=Shipment.Status.PUBLISHED,
        )
        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        load_ids = {item['id'] for item in r.json()['data']['loads']}
        self.assertIn(self.shipment.id, load_ids)
        self.assertNotIn(distant_local.id, load_ids)
        self.assertIn(distant_c2c.id, load_ids)
        self.assertNotIn(same_country_c2c.id, load_ids)

    @patch('api.views.get_latest_device_position')
    def test_load_discovery_returns_loads_in_created_at_desc_order(self, mock_position):
        mock_position.return_value = {
            'latitude': 24.8610,
            'longitude': 67.0020,
            'raw': {'id': 10},
        }
        shipment2 = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Second',
            pickup_lat=Decimal('24.8612000'),
            pickup_lon=Decimal('67.0015000'),
            pickup_country_code='AE',
            delivery_address='B2',
            cargo_type='General',
            vehicle_type_required='Flatbed',
            local=True,
            status=Shipment.Status.PUBLISHED,
        )
        shipment3 = Shipment.objects.create(
            shipper=self.shipper,
            pickup_address='Third',
            pickup_lat=Decimal('24.8615000'),
            pickup_lon=Decimal('67.0018000'),
            pickup_country_code='AE',
            delivery_address='B3',
            cargo_type='General',
            vehicle_type_required='Flatbed',
            local=True,
            status=Shipment.Status.PUBLISHED,
        )

        r = self.client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 200)
        loads = r.json()['data']['loads']
        self.assertGreaterEqual(len(loads), 3)
        load_ids = [l['id'] for l in loads]
        self.assertEqual(load_ids[:3], [shipment3.id, shipment2.id, self.shipment.id])


class FleetOwnerLoadDiscoveryApiTests(TestCase):
    def test_fleet_owner_without_tc_id_returns_400(self):
        shipper = User.objects.create_user(
            'fleet-disc-ship@test.com', email='fleet-disc-ship@test.com', password='secret',
        )
        UserRole.objects.create(user=shipper, role=UserRole.Role.SHIPPER, phone='', language='en')
        ShipperProfile.objects.create(user=shipper, account_type=ShipperProfile.AccountType.INDIVIDUAL)
        owner = User.objects.create_user(
            'fleet-disc-owner@test.com', email='fleet-disc-owner@test.com', password='secret',
        )
        UserRole.objects.create(user=owner, role=UserRole.Role.TRANSPORTER, phone='111', language='en')
        TransporterProfile.objects.create(
            user=owner,
            account_type=TransporterProfile.AccountType.FLEET_OWNER,
            local=True,
            country_to_country=False,
            documents_verified=True,
        )
        token, _ = Token.objects.get_or_create(user=owner)
        client = APIClient()
        client.credentials(HTTP_AUTHORIZATION=f'Bearer {token.key}')
        r = client.get('/api/transporter/load-discovery/?country_code=AE')
        self.assertEqual(r.status_code, 400)
        self.assertEqual(r.json()['error'], 'Missing tc_id on transporter profile.')

