from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings
from django.urls import reverse

from orders.models import Order, OrderItem
from products.models import Product

User = get_user_model()

SAMPLE_GIF = bytes.fromhex(
    '47494638396101000100800000000000ffffff21f90401000000002c00000000010001000002024401003b'
)


class OrderPaymentFlowTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username='alice', password='password123', email='alice@example.com')
        self.client.force_login(self.user)
        image = SimpleUploadedFile('product.gif', SAMPLE_GIF, content_type='image/gif')
        self.product = Product.objects.create(
            name='Wireless Mouse',
            description='A test product for order payments.',
            price='1250.00',
            listing_type='sale',
            image=image,
            category='Electronics',
            stock=8,
        )
        self.order = Order.objects.create(
            user=self.user,
            total_price='1250.00',
            full_name='Alice Example',
            phone='+9779800000000',
            address='Kathmandu',
        )
        OrderItem.objects.create(order=self.order, product=self.product, quantity=1, price='1250.00')

    def test_payment_page_does_not_fake_pay_order(self):
        response = self.client.get(reverse('payment', args=[self.order.id]))
        self.assertEqual(response.status_code, 200)
        self.order.refresh_from_db()
        self.assertEqual(self.order.payment_status, 'Unpaid')
        self.assertEqual(self.order.status, 'Pending')

    @override_settings(
        PAYMENT_CURRENCY='NPR',
        ESEWA_SANDBOX=True,
        ESEWA_PRODUCT_CODE='EPAYTEST',
        ESEWA_SECRET_KEY='secret-key',
        ESEWA_FORM_URL='https://esewa.test/form',
        ESEWA_STATUS_URL='https://esewa.test/status/',
    )
    def test_posting_esewa_returns_redirect_form_and_sets_pending_state(self):
        response = self.client.post(reverse('payment', args=[self.order.id]), {'payment': 'esewa'})

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'order/payment_redirect.html')
        self.assertContains(response, 'https://esewa.test/form')

        self.order.refresh_from_db()
        self.assertEqual(self.order.payment_provider, 'esewa')
        self.assertEqual(self.order.payment_method, 'eSewa')
        self.assertEqual(self.order.payment_status, 'Pending')
        self.assertEqual(self.order.payment_currency, 'NPR')
        self.assertTrue(self.order.payment_session_id.startswith(f'ORD-{self.order.id}-'))
        self.assertContains(response, self.order.payment_session_id)

    @override_settings(PAYMENT_CURRENCY='NPR')
    @patch('orders.views.verify_esewa_payment')
    def test_esewa_return_marks_order_paid_only_after_verified_status(self, mock_verify_esewa_payment):
        self.order.payment_method = 'eSewa'
        self.order.payment_provider = 'esewa'
        self.order.payment_status = 'Pending'
        self.order.payment_session_id = 'ORD-1-ABC123'
        self.order.payment_currency = 'NPR'
        self.order.save()

        mock_verify_esewa_payment.return_value = {
            'status': 'Paid',
            'reference': 'ESEWA-REF-123',
            'session_id': 'ORD-1-ABC123',
            'currency': 'NPR',
            'message': 'eSewa payment verified successfully.',
        }

        response = self.client.get(reverse('esewa_return') + f'?order_id={self.order.id}&data=dummy')

        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], reverse('success') + f'?order_id={self.order.id}')
        self.order.refresh_from_db()
        self.assertEqual(self.order.payment_status, 'Paid')
        self.assertEqual(self.order.status, 'Paid')
        self.assertEqual(self.order.payment_reference, 'ESEWA-REF-123')
        self.assertEqual(self.order.payment_provider, 'esewa')
        self.assertIsNotNone(self.order.paid_at)

    @override_settings(
        PAYMENT_CURRENCY='NPR',
        KHALTI_SECRET_KEY='test_secret',
        KHALTI_INITIATE_URL='https://khalti.test/initiate/',
        KHALTI_LOOKUP_URL='https://khalti.test/lookup/',
    )
    @patch('orders.views.initiate_khalti_payment')
    def test_posting_khalti_redirects_to_payment_url(self, mock_initiate_khalti_payment):
        mock_initiate_khalti_payment.return_value = {
            'payment_url': 'https://pay.khalti.test/session/123',
            'session_id': 'pidx_test_123',
            'reference': 'ORDER-1-TEST123',
            'currency': 'NPR',
        }

        response = self.client.post(reverse('payment', args=[self.order.id]), {'payment': 'khalti'})

        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], 'https://pay.khalti.test/session/123')
        self.order.refresh_from_db()
        self.assertEqual(self.order.payment_provider, 'khalti')
        self.assertEqual(self.order.payment_method, 'Khalti')
        self.assertEqual(self.order.payment_status, 'Pending')
        self.assertEqual(self.order.payment_session_id, 'pidx_test_123')
        self.assertEqual(self.order.payment_reference, 'ORDER-1-TEST123')

    @override_settings(PAYMENT_CURRENCY='NPR')
    @patch('orders.views.verify_khalti_payment')
    def test_khalti_return_marks_order_paid_only_after_verified_lookup(self, mock_verify_khalti_payment):
        self.order.payment_method = 'Khalti'
        self.order.payment_provider = 'khalti'
        self.order.payment_status = 'Pending'
        self.order.payment_session_id = 'pidx_test_123'
        self.order.payment_reference = 'ORDER-1-TEST123'
        self.order.payment_currency = 'NPR'
        self.order.save()

        mock_verify_khalti_payment.return_value = {
            'status': 'Paid',
            'reference': 'TXN-456',
            'session_id': 'pidx_test_123',
            'currency': 'NPR',
            'message': 'Khalti payment verified successfully.',
        }

        response = self.client.get(reverse('khalti_return') + f'?order_id={self.order.id}&pidx=pidx_test_123')

        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], reverse('success') + f'?order_id={self.order.id}')
        self.order.refresh_from_db()
        self.assertEqual(self.order.payment_status, 'Paid')
        self.assertEqual(self.order.status, 'Paid')
        self.assertEqual(self.order.payment_reference, 'TXN-456')
        self.assertEqual(self.order.payment_provider, 'khalti')
        self.assertIsNotNone(self.order.paid_at)

    def test_cash_on_delivery_does_not_mark_order_as_paid(self):
        response = self.client.post(reverse('payment', args=[self.order.id]), {'payment': 'cod'})

        self.assertEqual(response.status_code, 302)
        self.order.refresh_from_db()
        self.assertEqual(self.order.payment_method, 'Cash on Delivery')
        self.assertEqual(self.order.payment_status, 'Pending')
        self.assertEqual(self.order.status, 'Pending')


class CheckoutLocationFlowTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username='buyer', password='password123', email='buyer@example.com')
        self.user.profile.phone = '+9779811111111'
        self.user.profile.location_label = 'Baneshwor, Kathmandu'
        self.user.profile.default_address = 'Old Baneshwor Chowk\nKathmandu'
        self.user.profile.latitude = '27.693400'
        self.user.profile.longitude = '85.335000'
        self.user.profile.location_accuracy_m = '20.50'
        self.user.profile.save()

        image = SimpleUploadedFile('checkout.gif', SAMPLE_GIF, content_type='image/gif')
        self.product = Product.objects.create(
            name='Smart Watch',
            description='Checkout location test product.',
            price='2500.00',
            listing_type='sale',
            image=image,
            category='Electronics',
            stock=5,
        )
        self.client.force_login(self.user)
        session = self.client.session
        session['cart'] = {str(self.product.id): 1}
        session.save()

    def test_checkout_prefills_saved_profile_delivery_details(self):
        response = self.client.get(reverse('checkout'))

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['delivery']['phone'], '+9779811111111')
        self.assertEqual(response.context['delivery']['location_label'], 'Baneshwor, Kathmandu')
        self.assertEqual(response.context['delivery']['address'], 'Old Baneshwor Chowk\nKathmandu')
        self.assertEqual(response.context['delivery']['latitude'], '27.693400')
        self.assertEqual(response.context['delivery']['longitude'], '85.335000')

    def test_checkout_saves_order_delivery_snapshot_and_updates_profile(self):
        response = self.client.post(
            reverse('checkout'),
            {
                'full_name': 'Buyer Example',
                'phone': '+9779801234567',
                'location_label': 'Lalitpur, Kupondole',
                'address': 'Kupondole Height\nLalitpur',
                'delivery_notes': 'Call before reaching the gate',
                'latitude': '27.685100',
                'longitude': '85.316700',
                'location_accuracy_m': '12.75',
                'save_as_default': 'on',
            },
        )

        order = Order.objects.get(user=self.user)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], reverse('payment', args=[order.id]))
        self.assertEqual(order.phone, '+9779801234567')
        self.assertEqual(order.location_label, 'Lalitpur, Kupondole')
        self.assertEqual(order.delivery_notes, 'Call before reaching the gate')
        self.assertIsNotNone(order.latitude)
        self.assertIsNotNone(order.longitude)

        self.user.profile.refresh_from_db()
        self.assertEqual(self.user.profile.phone, '+9779801234567')
        self.assertEqual(self.user.profile.location_label, 'Lalitpur, Kupondole')
        self.assertEqual(self.user.profile.default_address, 'Kupondole Height\nLalitpur')

    def test_checkout_can_prefill_from_recent_delivery_shortcut(self):
        recent_order = Order.objects.create(
            user=self.user,
            total_price='2500.00',
            status='Delivered',
            payment_status='Paid',
            payment_method='Cash on Delivery',
            full_name='Buyer Example',
            phone='+9779809999999',
            address='Naxal Chowk\nKathmandu',
            location_label='Naxal, Kathmandu',
            latitude='27.717300',
            longitude='85.331900',
            location_accuracy_m='11.25',
            delivery_notes='Ring the bell once',
        )

        response = self.client.get(reverse('checkout') + f'?delivery_shortcut=order-{recent_order.id}')

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['delivery']['phone'], '+9779809999999')
        self.assertEqual(response.context['delivery']['location_label'], 'Naxal, Kathmandu')
        self.assertEqual(response.context['delivery']['address'], 'Naxal Chowk\nKathmandu')
        self.assertEqual(response.context['delivery']['delivery_notes'], 'Ring the bell once')
        self.assertEqual(response.context['delivery']['selected_shortcut_id'], f'order-{recent_order.id}')
        self.assertTrue(any(shortcut['selected'] for shortcut in response.context['delivery_shortcuts']))

    def test_checkout_save_as_default_clears_stale_profile_gps_when_manual_address_has_no_coordinates(self):
        response = self.client.post(
            reverse('checkout'),
            {
                'full_name': 'Buyer Example',
                'phone': '+9779801234567',
                'location_label': 'Bhaktapur Durbar Area',
                'address': 'Taumadhi Square\nBhaktapur',
                'delivery_notes': 'Call after arrival',
                'save_as_default': 'on',
            },
        )

        order = Order.objects.get(user=self.user)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], reverse('payment', args=[order.id]))

        self.user.profile.refresh_from_db()
        self.assertEqual(self.user.profile.default_address, 'Taumadhi Square\nBhaktapur')
        self.assertIsNone(self.user.profile.latitude)
        self.assertIsNone(self.user.profile.longitude)
        self.assertIsNone(self.user.profile.location_accuracy_m)
        self.assertIsNone(self.user.profile.location_updated_at)

