from decimal import Decimal

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
from unittest.mock import patch

from stock import models as stock_models
from business import models as business_models, services

from .fixtures import create_sales_fixtures


class SalesReturnServiceTestCase(TestCase):
    def setUp(self):
        (
            self.merchant,
            self.customer,
            self.warehouse_strict,
            self.warehouse_relaxed,
            self.warehouse_strict_out,
            self.product,
            self.operator,
        ) = create_sales_fixtures()
        User = get_user_model()
        self.user = User.objects.create_user(username='sales-return', password='pass123')
        self.strict_items = [
            {'product_id': self.product.id, 'numbers': [6, 2], 'price': '20.0', 'unit': '米'}
        ]

    def test_create_sales_return_order_success(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=[{'product_id': self.product.id, 'quantity': 50, 'num_of_rolls': 2, 'price': '19.5'}],
        )
        self.assertEqual(order.status, business_models.SalesReturnStatusEnum.PENDING)
        item = order.items.first()
        self.assertEqual(item.split_quantity_of_rolls(), [])

    def test_sales_return_order_human_id_generation(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=[{'product_id': self.product.id, 'quantity': 50, 'num_of_rolls': 2, 'price': '19.5'}],
            created_by=self.user,
        )
        self.assertIsNotNone(order.human_id)
        self.assertEqual(
            order.human_id,
            f"XT{order.created_at.strftime('%Y%m%d')}{order.id:06d}",
        )

    def test_review_sales_return_order_updates_balance(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )
        self.assertEqual(order.items.first().split_quantity_of_rolls(), [6, 2])
        with patch('business.services.create_sales_return_order_stock_entries.delay') as mock_delay:
            reviewed = services.review_sales_return_order(
                sales_return_order=order,
                target_status=business_models.SalesReturnStatusEnum.APPROVED,
                reviewed_by=self.user,
            )
        self.assertEqual(reviewed.status, business_models.SalesReturnStatusEnum.APPROVED)
        mock_delay.assert_called_once()
        balance = business_models.CustomerBalance.objects.get(
            merchant=self.merchant,
            customer=self.customer,
        )
        self.assertLess(balance.balance, Decimal('0'))
        record = business_models.BalanceChangeRecord.objects.get(
            merchant=self.merchant,
            source_type=business_models.BalanceChangeSourceEnum.SALES_RETURN_ORDER,
            source_id=order.id,
        )
        self.assertEqual(record.direction, business_models.BalanceChangeDirectionEnum.DECREASE)

    def test_sales_return_cancel_blocked_after_stock_created(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=[{'product_id': self.product.id, 'quantity': 15, 'num_of_rolls': 1, 'price': '18.0'}],
        )
        stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse_relaxed,
            source_type=stock_models.StockChangeSourceEnum.SALES_RETURN,
            source_id=order.id,
        )
        with self.assertRaises(ValueError):
            services.review_sales_return_order(
                sales_return_order=order,
                target_status=business_models.SalesReturnStatusEnum.CANCELLED,
            )

    def test_sales_return_relaxed_mode_stock_payload_preserves_rolls(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=[{'product_id': self.product.id, 'quantity': 100, 'num_of_rolls': 1, 'price': '22.0'}],
        )
        stock_flow_items = services._build_stock_flow_items_from_order(order)
        self.assertEqual(len(stock_flow_items), 1)
        self.assertEqual(stock_flow_items[0]['num_of_rolls'], 1)

    def test_sales_return_relaxed_mode_even_split(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=[{'product_id': self.product.id, 'quantity': 100, 'num_of_rolls': 3, 'price': '22.0'}],
            created_by=self.user,
        )
        stock_flow_items = services._build_stock_flow_items_from_order(order)
        payload = services.create_sales_return_order_stock_entries_sync(
            sales_return_order_id=order.id,
            warehouse_id=self.warehouse_relaxed.id,
            items=stock_flow_items,
            created_by_id=self.user.id,
        )
        record = stock_models.StockChangeRecord.objects.get(id=payload['stock_change_record_id'])
        quantities = sorted([detail.quantity for detail in record.details.order_by('id')], reverse=True)
        self.assertEqual(quantities, [Decimal('33.34'), Decimal('33.33'), Decimal('33.33')])

    def test_update_sales_return_order_success(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )
        updated = services.update_sales_return_order(
            sales_return_order=order,
            items=self.strict_items,
            remarks='updated',
        )
        self.assertEqual(updated.remarks, 'updated')
        self.assertEqual(updated.items.count(), 1)
        self.assertEqual(updated.items.first().split_quantity_of_rolls(), [6, 2])

    def test_update_sales_return_order_rejects_non_pending(self):
        order = services.create_sales_return_order(
            merchant=self.merchant,
            customer=self.customer,
            return_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )
        order.status = business_models.SalesReturnStatusEnum.APPROVED
        order.save(update_fields=['status'])
        with self.assertRaises(ValueError):
            services.update_sales_return_order(
                sales_return_order=order,
                items=self.strict_items,
            )
