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, UserRole
from core.models import CountryBoundary, FreightRoute, Shipment, Zone
from core.pricing.backends import ShapelyBackend
from core.pricing.service import calculate_suggested_price, country_code_for_point

User = get_user_model()


# Simple square covering ~lon 54–56, lat 24–26 (rough AE area stub).
AE_SQUARE = {
    'type': 'Polygon',
    'coordinates': [[
        [54.0, 24.0],
        [56.0, 24.0],
        [56.0, 26.0],
        [54.0, 26.0],
        [54.0, 24.0],
    ]],
}

# Disjoint stubs so PK and AF never share a point.
PK_SQUARE = {
    'type': 'Polygon',
    'coordinates': [[
        [66.0, 24.0],
        [70.0, 24.0],
        [70.0, 30.0],
        [66.0, 30.0],
        [66.0, 24.0],
    ]],
}

AF_SQUARE = {
    'type': 'Polygon',
    'coordinates': [[
        [66.0, 32.0],
        [70.0, 32.0],
        [70.0, 38.0],
        [66.0, 38.0],
        [66.0, 32.0],
    ]],
}


class ShapelyBackendTests(TestCase):
    def test_intersect_route_returns_positive_km(self):
        boundary = CountryBoundary(
            country_code='AE',
            country_name='United Arab Emirates',
            geojson=AE_SQUARE,
        )
        route = [(54.5, 24.5), (55.5, 25.5)]
        segments = ShapelyBackend().intersect_route_with_boundaries(route, [boundary])
        self.assertEqual(len(segments), 1)
        self.assertEqual(segments[0]['country_code'], 'AE')
        self.assertGreater(segments[0]['km'], 0)


class PricingServiceTests(TestCase):
    def setUp(self):
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'), rate_per_km=Decimal('2.00'))
        CountryBoundary.objects.create(
            country_code='AE',
            country_name='United Arab Emirates',
            geojson=AE_SQUARE,
        )

    def test_calculate_suggested_price(self):
        route = [(54.5, 24.5), (55.5, 25.5)]
        price, breakdown = calculate_suggested_price(route)
        self.assertEqual(len(breakdown), 1)
        self.assertEqual(breakdown[0]['country_code'], 'AE')
        expected = (Decimal(str(breakdown[0]['km'])) * Decimal('2.00')).quantize(Decimal('0.01'))
        self.assertEqual(price, expected)

    def test_raises_without_boundaries(self):
        CountryBoundary.objects.all().delete()
        with self.assertRaises(ValueError):
            calculate_suggested_price([(54.5, 24.5), (55.5, 25.5)])

    def test_country_code_for_point_uses_boundary(self):
        self.assertEqual(country_code_for_point(25.0, 55.0), 'AE')


class ShipmentPricingApiTests(TestCase):
    def setUp(self):
        self.shipper = User.objects.create_user('price@test.com', email='price@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,
            kyc_verified=True,
        )
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'), rate_per_km=Decimal('1.50'))
        CountryBoundary.objects.create(
            country_code='AE',
            country_name='United Arab Emirates',
            geojson=AE_SQUARE,
        )
        token, _ = Token.objects.get_or_create(user=self.shipper)
        self.client = APIClient()
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token.key}')

    @patch('api.serializers.ShipmentCreateUpdateSerializer._ensure_operational_country_codes')
    @patch(
        'api.serializers.ShipmentCreateUpdateSerializer._fill_country_codes_from_coords',
        side_effect=lambda *args: args[-1],
    )
    def test_create_stores_client_suggested_price(self, _fill, _ops):
        response = self.client.post(
            '/api/shipper/shipments/',
            {
                'pickup_address': 'A',
                'pickup_lat': '24.5000000',
                'pickup_lon': '54.5000000',
                'pickup_country_code': 'AE',
                'delivery_address': 'B',
                'delivery_lat': '25.5000000',
                'delivery_lon': '55.5000000',
                'delivery_country_code': 'AE',
                'cargo_type': 'General',
                'weight': '1 ton',
                'vehicle_type_required': 'Flatbed',
                'local': True,
                'country_to_country': False,
                'suggested_price': '1500.00',
            },
            format='json',
        )
        self.assertEqual(response.status_code, 201, response.content)
        data = response.json()['data']
        self.assertEqual(data['suggested_price'], '1500.00')
        shipment = Shipment.objects.get(pk=data['id'])
        self.assertEqual(shipment.suggested_price, Decimal('1500.00'))
        self.assertEqual(data['currency'], 'AED')
        self.assertEqual(shipment.currency, 'AED')

    @patch('api.serializers.ShipmentCreateUpdateSerializer._ensure_operational_country_codes')
    @patch(
        'api.serializers.ShipmentCreateUpdateSerializer._fill_country_codes_from_coords',
        side_effect=lambda *args: args[-1],
    )
    def test_create_ignores_payload_currency_and_uses_pickup_country(self, _fill, _ops):
        response = self.client.post(
            '/api/shipper/shipments/',
            {
                'pickup_address': 'A',
                'pickup_lat': '24.5000000',
                'pickup_lon': '54.5000000',
                'pickup_country_code': 'AE',
                'delivery_address': 'B',
                'delivery_lat': '25.5000000',
                'delivery_lon': '55.5000000',
                'delivery_country_code': 'AE',
                'cargo_type': 'General',
                'weight': '1 ton',
                'vehicle_type_required': 'Flatbed',
                'local': True,
                'country_to_country': False,
                'suggested_price': '1500.00',
                'currency': 'USD',
            },
            format='json',
        )
        self.assertEqual(response.status_code, 201, response.content)
        data = response.json()['data']
        self.assertEqual(data['currency'], 'AED')
        shipment = Shipment.objects.get(pk=data['id'])
        self.assertEqual(shipment.currency, 'AED')


class ShipmentPriceEstimateApiTests(TestCase):
    def setUp(self):
        self.shipper = User.objects.create_user(
            'estimate@test.com',
            email='estimate@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,
            kyc_verified=True,
        )
        Zone.objects.create(country_code='AE', radius_km=Decimal('50'), rate_per_km=Decimal('1.50'))
        CountryBoundary.objects.create(
            country_code='AE',
            country_name='United Arab Emirates',
            geojson=AE_SQUARE,
        )
        token, _ = Token.objects.get_or_create(user=self.shipper)
        self.client = APIClient()
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token.key}')
        self.url = '/api/shipper/shipments/estimate-price/'

    def test_estimate_price_from_pickup_delivery(self):
        response = self.client.post(
            self.url,
            {
                'pickup_lat': '24.5000000',
                'pickup_lon': '54.5000000',
                'delivery_lat': '25.5000000',
                'delivery_lon': '55.5000000',
            },
            format='json',
        )
        self.assertEqual(response.status_code, 200, response.content)
        data = response.json()['data']
        self.assertIn('suggested_price', data)
        self.assertIn('distance_km', data)
        self.assertTrue(data['price_breakdown'])
        self.assertEqual(data['price_breakdown'][0]['country_code'], 'AE')
        self.assertEqual(data['currency'], 'AED')
        self.assertEqual(Shipment.objects.count(), 0)

    def test_estimate_price_accepts_route_coords(self):
        response = self.client.post(
            self.url,
            {
                'pickup_lat': '24.5000000',
                'pickup_lon': '54.5000000',
                'delivery_lat': '25.5000000',
                'delivery_lon': '55.5000000',
                'route_coords': [[54.5, 24.5], [55.0, 25.0], [55.5, 25.5]],
            },
            format='json',
        )
        self.assertEqual(response.status_code, 200, response.content)
        data = response.json()['data']
        self.assertIsNotNone(data['suggested_price'])
        self.assertTrue(data['price_breakdown'])

    def test_estimate_price_requires_coords(self):
        response = self.client.post(self.url, {}, format='json')
        self.assertEqual(response.status_code, 400)
        error = response.json()['error']
        self.assertIn('pickup_lat', error)
        self.assertIn('delivery_lon', error)

    @patch('core.geocoding.reverse_geocode_country_code', return_value=None)
    def test_estimate_price_outside_zones(self, _geocode):
        response = self.client.post(
            self.url,
            {
                'pickup_lat': '10.0000000',
                'pickup_lon': '10.0000000',
                'delivery_lat': '11.0000000',
                'delivery_lon': '11.0000000',
            },
            format='json',
        )
        self.assertEqual(response.status_code, 400)
        self.assertIn('Could not calculate price', response.json()['message'])


class CrossBorderFreightEstimateApiTests(TestCase):
    def setUp(self):
        self.shipper = User.objects.create_user(
            'freight-est@test.com',
            email='freight-est@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,
            kyc_verified=True,
        )
        CountryBoundary.objects.create(
            country_code='PK',
            country_name='Pakistan',
            geojson=PK_SQUARE,
        )
        CountryBoundary.objects.create(
            country_code='AF',
            country_name='Afghanistan',
            geojson=AF_SQUARE,
        )
        token, _ = Token.objects.get_or_create(user=self.shipper)
        self.client = APIClient()
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token.key}')
        self.url = '/api/shipper/shipments/estimate-price/'
        self.pk_to_af = {
            'pickup_lat': '25.0000000',
            'pickup_lon': '67.0000000',
            'delivery_lat': '34.0000000',
            'delivery_lon': '67.0000000',
        }
        self.af_to_pk = {
            'pickup_lat': '34.0000000',
            'pickup_lon': '67.0000000',
            'delivery_lat': '25.0000000',
            'delivery_lon': '67.0000000',
        }

    def test_pk_to_af_uses_directional_min_freight(self):
        FreightRoute.objects.create(
            origin_country_code='PK',
            destination_country_code='AF',
            min_freight=Decimal('1500.00'),
            currency='USD',
        )
        FreightRoute.objects.create(
            origin_country_code='AF',
            destination_country_code='PK',
            min_freight=Decimal('900.00'),
            currency='USD',
        )
        response = self.client.post(self.url, self.pk_to_af, format='json')
        self.assertEqual(response.status_code, 200, response.content)
        data = response.json()['data']
        self.assertEqual(data['suggested_price'], '1500.00')
        self.assertEqual(data['origin_country_code'], 'PK')
        self.assertEqual(data['destination_country_code'], 'AF')
        self.assertEqual(data['currency'], 'PKR')
        self.assertEqual(data['price_breakdown'], [])
        self.assertIsNotNone(data['distance_km'])

    def test_missing_pair_returns_null_price(self):
        FreightRoute.objects.create(
            origin_country_code='AF',
            destination_country_code='PK',
            min_freight=Decimal('900.00'),
            currency='USD',
        )
        response = self.client.post(self.url, self.pk_to_af, format='json')
        self.assertEqual(response.status_code, 200, response.content)
        data = response.json()['data']
        self.assertIsNone(data['suggested_price'])
        self.assertEqual(data['origin_country_code'], 'PK')
        self.assertEqual(data['destination_country_code'], 'AF')
        self.assertEqual(data['currency'], 'PKR')
        self.assertEqual(data['price_breakdown'], [])

    def test_af_to_pk_does_not_use_reverse_route(self):
        FreightRoute.objects.create(
            origin_country_code='AF',
            destination_country_code='PK',
            min_freight=Decimal('900.00'),
            currency='USD',
        )
        response = self.client.post(self.url, self.af_to_pk, format='json')
        self.assertEqual(response.status_code, 200, response.content)
        data = response.json()['data']
        self.assertEqual(data['suggested_price'], '900.00')
        self.assertEqual(data['origin_country_code'], 'AF')
        self.assertEqual(data['destination_country_code'], 'PK')
        self.assertEqual(data['currency'], 'AFN')
